Skip to content

Conversation

@asp2286
Copy link

@asp2286 asp2286 commented Jan 15, 2026

Fixes #639

Why

The main Mapster package currently only targets net8.0+, which prevents consumers on .NET Framework 4.8 (and other netstandard2.0-based targets) from upgrading.

What changed

  • Multi-target Mapster to netstandard2.0 (src/Mapster/Mapster.csproj).
  • Replace usages of newer BCL APIs that are not available on netstandard2.0 (LINQ ExceptBy / IntersectBy, range indexing).
  • Avoid compile-time dependency on RequiredMemberAttribute (string-based attribute check keeps behavior on newer TFMs while allowing netstandard2.0 compilation).
  • Add minimal shims for nullable flow-analysis attributes and IsExternalInit for netstandard2.0.
  • Add netstandard2.0-only references required for runtime code generation/dynamic binder (System.Reflection.Emit*, Microsoft.CSharp).

Validation

  • dotnet build src/Mapster/Mapster.csproj -c Release -f netstandard2.0
  • dotnet test src/Mapster.Tests/Mapster.Tests.csproj -c Release -f net8.0

@DocSvartz
Copy link
Contributor

Hi @asp2286. Have you tested this in environments where only .NET Framework 4.8 is available?
We tried this before #655, but some tests failed in .NET Framework 4.8.

@asp2286
Copy link
Author

asp2286 commented Jan 16, 2026

CI on ubuntu-latest fails trying to run net48 test targets because the runner image no longer has mono ("Could not find 'mono' host").

This PR now gates net48 in test projects behind '' == 'Windows_NT', so dotnet test ./src/Mapster.sln on Linux runs only net8/9/10 TFMs. (Commit: d2cfe27)

@asp2286
Copy link
Author

asp2286 commented Jan 16, 2026

Correction: the MSBuild property is $(OS) (previous comment lost it due to shell expansion).

Net48 test targets are now gated behind $(OS) == Windows_NT, so dotnet test ./src/Mapster.sln on ubuntu-latest runs only net8/9/10 TFMs.

@asp2286
Copy link
Author

asp2286 commented Jan 16, 2026

Hi @asp2286. Have you tested this in environments where only .NET Framework 4.8 is available? We tried this before #655, but some tests failed in .NET Framework 4.8.

Hi @DocSvartz.
Unit tests are green on both environments (screenshots attached):

Windows (x86): including .NET Framework 4.8 (net48) (also net8.0/net9.0/net10.0) — 1,489 tests (0 failed)
macOS (arm64): all solution tests — 1,122 tests (0 failed)
image
image

@DocSvartz
Copy link
Contributor

@asp2286 Ok 👍

LangVersion == lastest not recommended for use.
Did you replace it as a solution to some problem?

@asp2286
Copy link
Author

asp2286 commented Jan 16, 2026

@asp2286 Ok 👍

LangVersion == lastest not recommended for use. Did you replace it as a solution to some problem?

@DocSvartz
Good call — I had LangVersion=latest there mostly out of habit, not to address a specific issue in this PR. I’ve updated it to LangVersion=12 instead so it’s pinned and won’t change behavior with future SDK updates.

Fixes MapsterMapper#639

Includes packaging changes to ship a netstandard2.0 assembly again, plus test/CI updates to keep net48 tests running only on Windows.
@asp2286 asp2286 force-pushed the fix/netstandard2-0 branch from 1b59b75 to 15a8f22 Compare January 16, 2026 12:06
@DocSvartz
Copy link
Contributor

@asp2286 👍

@andrerav @stagep @DevTKSS What do you think about this?
This could be released as the next pre-release version with netstandard2.0 support (though without the Mapster Tool 😅) if you don't mind?

@DevTKSS
Copy link
Collaborator

DevTKSS commented Jan 16, 2026

@DocSvartz @asp2286 would prefer, if you would:

  1. use rather #if NET8_OR_GREATER Conditionals and the #elif with suitable other one(s) Identifyer for backwards compartiblity to netstandard2.0 and Framework

  2. Do not replace current net8.0+ code with lower version/tfm code then instead add the condition enclosed code needed for the netstandard2.0

    If you would merge it like now, you would maybe possibly improve it for the framework and standard users, but potentially downgrade user experience and feature/performance for users of latest stable and future .NET version which we should target to avoid!

  3. Do not combine LangVersion upgrade changes (upgrades especially) in the same PR as your primary feature/fix here(!!!) this is likely to introduce unexpected changes!

    I used to develop with an Assembly that where requiring NET Framework 4.0 compartiblity and there were users of it, which managed to create working apps up to .net Core 5/6 but higher targeting ones did mostly run into unexpected errors! So if you upgrade here the same time to the LangVersion of 12, this potentially will happen here too. That is one strong argument for even have the LangVersion usage removed completely and rather depend on the tfm automatic provided LangVersion! This will also help you with this PR, if this is would be done&merged before this PR, so you can clearly depend on it. You will get editor version selection in your IDE too and the compiler will be much more helpful then cause you and everyone else working with this code way less headaches when it comes to bugfixing!

@DocSvartz
Copy link
Contributor

@DevTKSS I might be wrong, but potential incompatibility can occur if the machine has a runtime that doesn't support a LangVersion higher than the one set during compilation?
Example:
We're building on a Net 10 machine using LangVersion 14 for TFM (8, 9, 10), so something might go wrong with the Net8 build run to machine have runtime 8.0 only (not 10 runtime).

@DevTKSS
Copy link
Collaborator

DevTKSS commented Jan 16, 2026

@DocSvartz you can use net8 with all lower but if the user is not having net8+ then instead maybe only framework, he will get errors when the project here is using a specific hogher lang version. Thats why I would not set it explicitly then instead rely on the net standard 2.0 to provide all it needs and only those things it is meant to be capable of!

@DocSvartz
Copy link
Contributor

@DevTKSS Yes, But then all the code would have to be downgrade to like netstandard2.0.

And only those parts that will give performance in the new TFMs can be rewritten using #if NET8_OR_GREATER

Otherwise, it will be a spaghetti monster of #if constructions 😅 (at least netstandard2.0 no nullable reference type)

@DevTKSS
Copy link
Collaborator

DevTKSS commented Jan 16, 2026

Yes, But then all the code would have to be downgrade to like netstandard2.0.

@DocSvartz 🤔 ehm... but this downgrading seems to be exactly what this PR suggests, "just" to have the netstandard2.0 and Framework compartibility:
copying from the OP above:

Replace usages of newer BCL APIs that are not available on netstandard2.0 (LINQ ExceptBy / IntersectBy, range indexing).
Avoid compile-time dependency on RequiredMemberAttribute (string-based attribute check keeps behavior on newer TFMs while allowing netstandard2.0 compilation).

and <LangVersion>12</LangVersion>:

https://github.com/MapsterMapper/Mapster/pull/849/changes#diff-1b7ae41fe8a27384133f2ff6073cefe0c4b28486b1f0434ccc8710f3a3c7d562R17

and the "Spagetti Monster" how you call the Conditionals is not as worse as it seems:

https://github.com/MapsterMapper/Mapster/pull/849/changes#diff-6e5285bab587cf35cab1768f75b5e387e5a1b6f26f194bae155ba0953d41da12L284-L301

and the "Spagetti Monster" how you call the Conditionals is not as worse as it seems. Of course, it adds some lines, but in my opinion this is simply the price of keeping legacy targets (even Microsoft does not actively bring out Framework features anymore) and let's assume realistically, this PR is about the Mapster Project, so when will the first one come "hey, you introduced this compartibility Layer for Framework, can you do the same for the other parts too?" then what? Do you want to downgrade the whole Mapster Solution? just asking 🤷

https://github.com/MapsterMapper/Mapster/pull/849/changes#diff-6e5285bab587cf35cab1768f75b5e387e5a1b6f26f194bae155ba0953d41da12L284-L301

But yes, I totally can understand them too! I was also annoyed of e.g. the cool Source Generators like CommunityToolkit.Mvvm was having has not been supported or relyable use-able with that Framework App I tryed to enhance with it!
That is why I don't say, "Dont introduce netstandard2.0 Multi Targeting" then instead only ask for seperating the both Upgrades (LangVersion - which I still see potentially problematic to use in this setup! - and the Multi Targeting itself) cleanly and use the Conditionals with then proper IDE supported LangVersion Feature Lintings so we even get warned from the compiler before problems occur

@DocSvartz
Copy link
Contributor

DocSvartz commented Jan 16, 2026

Project, so when will the first one come "hey, you introduced this compartibility Layer for Framework, can you do the same for the other parts too?" then what? Do you want to downgrade the whole Mapster Solution? just asking 🤷
That is why I don't say, "Dont introduce netstandard2.0 Multi Targeting"

@DevTKSS I'm not against multi-targeting either.
But multi-targeting leads to two approaches:

  1. The development of new features should take into account the capabilities of the minimum target ("effectively a "downgrade to the minimum target" );
  2. Backporting new features to the minimum targeting ("Spagetti Monster" and various APIs if the feature can't be transferred).

As far as I understand, you are suggesting to adhere to approach number 2?

Example

#if NET8_0_OR_GREATER 
#nullable enable     
#endif   
// other code

#if NET8_0_OR_GREATER 
        protected Expression? TryRestoreRecordMember(IMemberModelEx member, ClassModel? restorRecordModel, Expression? destination)
        {
#else
        protected Expression TryRestoreRecordMember(IMemberModelEx member, ClassModel restorRecordModel, Expression destination)
        {
#endif   
            if (restorRecordModel != null && destination != null)
            {
                var find = restorRecordModel.Members
                               .Where(x => x.Name == member.Name).FirstOrDefault();

                if (find != null)
                {
                    var compareNull = Expression.Equal(destination, Expression.Constant(null, destination.Type));
#if NET10_0_OR_GREATER
                    return Expression.Condition(compareNull, member.Type.CreateExprDefault, Expression.MakeMemberAccess(destination, (MemberInfo)find.Info));
#else
                    return Expression.Condition(compareNull, member.Type.CreateDefault(), Expression.MakeMemberAccess(destination, (MemberInfo)find.Info));
#endif

                }

            }

            return null;
        }


@asp2286
Copy link
Author

asp2286 commented Jan 18, 2026

@DocSvartz, @DevTKSS
Hi guys!

Regarding LangVersion

LangVersion is a compile-time setting only and does not affect runtime compatibility.
Using C# 12 is safe for netstandard2.0 and net48: nullable annotations and other newer language features compile to metadata that older runtimes simply ignore.

A few important points for context:

C# 7.3 does not support nullable reference types.

The Mapster codebase already uses C# 8+ features (nullable reference types, notnull constraint, etc.) in projects targeting netstandard2.0 / net48.

Without an explicit LangVersion, the SDK defaults those TFMs to C# 7.3, which fails to compile.

All supported .NET Framework versions can consume IL produced by C# 12, since language support depends on the Roslyn compiler, not on the target runtime.

Visual Studio 2022 (17.7+) fully supports C# 12 and is the current recommended development environment.

I have locally tested LangVersion=12 from .NET Framework 3.5 SP1 through 4.8.1 in Visual Studio 2022 — builds and runtime behavior are correct.

The only potential limitation is Visual Studio 2019 (max C# 9), but its mainstream support ended in April 2024.

Given that the codebase already requires C# 8+ semantics, explicitly setting LangVersion reflects the existing reality and avoids silent SDK defaults that lead to build failures on netstandard2.0 / net48.

Could you please let me know what approach you’d prefer here?
I’m happy to align with your decision.

@asp2286
Copy link
Author

asp2286 commented Jan 18, 2026

@DocSvartz @asp2286 would prefer, if you would:

  1. use rather #if NET8_OR_GREATER Conditionals and the #elif with suitable other one(s) Identifyer for backwards compartiblity to netstandard2.0 and Framework
  2. Do not replace current net8.0+ code with lower version/tfm code then instead add the condition enclosed code needed for the netstandard2.0
    If you would merge it like now, you would maybe possibly improve it for the framework and standard users, but potentially downgrade user experience and feature/performance for users of latest stable and future .NET version which we should target to avoid!
  3. Do not combine LangVersion upgrade changes (upgrades especially) in the same PR as your primary feature/fix here(!!!) this is likely to introduce unexpected changes!
    I used to develop with an Assembly that where requiring NET Framework 4.0 compartiblity and there were users of it, which managed to create working apps up to .net Core 5/6 but higher targeting ones did mostly run into unexpected errors! So if you upgrade here the same time to the LangVersion of 12, this potentially will happen here too. That is one strong argument for even have the LangVersion usage removed completely and rather depend on the tfm automatic provided LangVersion! This will also help you with this PR, if this is would be done&merged before this PR, so you can clearly depend on it. You will get editor version selection in your IDE too and the compiler will be much more helpful then cause you and everyone else working with this code way less headaches when it comes to bugfixing!

@DevTKSS Fully agree — I’ll rework the PR to keep modern NET8+ code paths intact and isolate netstandard2.0 fallbacks in centralized compat helpers.

@asp2286
Copy link
Author

asp2286 commented Jan 18, 2026

@DocSvartz, @DevTKSS
Screenshot 2026-01-18 142910

One more clarification regarding language version vs. platform support (I’ve attached a screenshot from Microsoft’s official lifecycle page for reference).

I understand that C# 12 cannot be used with .NET Core 5/6, as those SDKs are tied to older Roslyn versions (C# 9 / C# 10 respectively).
However, both .NET 5 and .NET 6 are out of support at this point.

In contrast, .NET Framework (including 4.8 / 4.8.1 and even 3.5 SP1) is still in Active support according to Microsoft’s official lifecycle policy (see screenshot).

This highlights an important distinction:

Language version compatibility depends on the compiler (Roslyn), not the runtime.

Actively supported .NET Framework versions can be built with modern C# (including C# 12) when using a current toolchain such as Visual Studio 2022.

Older .NET Core versions (5/6) are out of support and therefore not a compatibility target, even though they technically cap the language version.

Given that Mapster already requires C# 8+ semantics, targeting a modern compiler aligns better with actively supported platforms and avoids optimizing for deprecated SDKs.

Happy to follow whatever direction you prefer here — just wanted to clarify the support-status angle.

@DocSvartz
Copy link
Contributor

@asp2286 @DevTKSS
If LangVersion problems are only related to the fact that Mapster can't be compiled using the old compiler (only compile errors), then from my point of view this is not a problem at all.

All supported .NET Framework versions can consume IL produced by C# 12, since language support depends on the Roslyn compiler, not on the target runtime.

Perhaps the runtimes for these environments have already been updated, or in C#12 does not use anything that could cause a failure. 🤔

Because the warning for the latest version states the following:

it enables language features that may require runtime or library features not included in the current SDK.

and this

you don't use a language that requires types or runtime behavior not available in your target framework. Choosing a language version newer than the default can cause hard to diagnose compile-time and runtime errors.

@DevTKSS
Copy link
Collaborator

DevTKSS commented Jan 18, 2026

@asp2286 I don't know about this LangVersion compile time difference you are mentioning and I didn't found a official resource, with statement of Microsoft for example, about e.g. a netstandard2.0 tfm or framework (not sure which tfm id this dod have) target is supporting .NET 8+ Lang features 🤔
My information is always, that for example Roslyn Generator projects should not upgrade the LangVersion, which is even linted from the IDE😅 so resulting of this, I would neither upgrade by setting the LangVersion manually in any netstandard 2.0 project like this. Same I wouldn't do this for NET 8 tfms to upgrade it to net 10 lang features but maybe this is less problematic then suddenly trying to provide LangVersion for NET 8+ to potentially net framework specifying projects just because this LangVersion didn't broke something (as far as we know, I guess) until now

@DocSvartz yes quite like your example, just I would prefer not to rename API maybe 🤔

But Backporting is in my opinion maybe the wrong wording? I mean I am not as deep into publishing packages, but assuming we are building a project and then pack it, when using the constants and not using LangVersion (directly or indirect through Directory.Build.props), this should normally work for ensuring this just like you can use the IDE editor tfm context drop-down on the top, to switch between netstandard 2 and net 8 and so on, which then causes the excluded code part to get greyed out and the ide and build lets you know about unsupported things and things we might need to provide a netstandard compartible code for too.

Or do we have any unknown constants for LangVersion I am not aware of, to avoid trying to bring net8.0 to net framework?🤔

So Its not about a decision like:

  1. Set LangVersion to the netstandard version/framework compartible version and downgrade the codebase (yes this is something I would strongly avoid but seeing this as what the PR seemed to introduce)
  2. Drop netstandard and framework (not even possible for netstandard because of the mapster source generator projects 🤣) in favor of new features and upgrade LangVersion to highest possible

Then only providing the best for both worlds by:

  • dropping the usage of LangVersion and rely on the tfm provided LangVersion

  • use conditionals in the code to decide what needs to be enabled when (like you correctly shown in the example)

  • ensuring correct and stable but tfm provided conditional LangVersion supported Mapster packages.

    • If something is not supported by netstandard -> add a conditional to provide code which will work for this tfm as alternative code path👍
    • If we got a new lang feature provided vy e.g. net9 the collection expression, if I remember correctly, and using this introduces important improvement/ide lints this to be preferred, use a conditional for NET9.0_OR_GREATER 👍 that has this different way. But I would not enforce collection expression in existing code if we don't get a realistic reason for it or we are either way touching this code.
  • ensuring null ability and null awareness across the code base if you touch code which is not so far set up for this and issues this in Codeanalysis! Please check if we can, at least for the tfm net8+😅 it's making life easier.

@DocSvartz
Copy link
Contributor

DocSvartz commented Jan 18, 2026

@asp2286 @DevTKSS
If NetStandard build with not fails with 12 LangVersion

You can add the following:


<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<LangVersion>12</LangVersion>
</PropertyGroup>

Then the other TFMs will use the LangVersion set for it. And in netstandard2.0, the manually set one 🤔

Or using a version that allows you to use Nullable reference type. To simply avoid rewriting the parts in which they are used

@DocSvartz
Copy link
Contributor

I don't know about this LangVersion compile time difference you are mentioning and I didn't found a official resource, with statement of Microsoft for example, about e.g. a netstandard2.0 tfm or framework (not sure which tfm id this dod have) target is supporting .NET 8+ Lang features 🤔

@DevTKSS
According to the description, LangVersion is a compiler setting to not accept language constructs above a certain version. Therefore, if the build succeeds on the machine you're compiling on, there shouldn't be any problems.

However, the same documentation mentions possible runtime issues, suggesting some kind of duality to this setting.🤔
🤪

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants