I have asked this question today. Yes, it is possible. Here is the proof:
using System;
using System.Linq;
using System.Runtime.CompilerServices;
//C# 10
namespace ConsoleApp1;
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello C# 10");
CallerArgumentExpressionDemo(args.Length == 0 && (args.FirstOrDefault() ?? "42") == "42");
int x = 0;
//C# 10 - Assignment and declaration in same deconstruction
(x, string y) = new DemoStructRecord(2,"apple");
Console.WriteLine($"{x}, {y}");
}
//C# 10 - CallerArgumentExpression
public static void CallerArgumentExpressionDemo(bool b, [CallerArgumentExpression("b")] string message = null)
{
if (b)
{
Console.WriteLine($"Message from the caller expression: {message}");
}
}
}
//C# 10 - record struct
record struct DemoStructRecord(int A, string B)
{
private const string SomeConstant = "Something";
//C# 10 - Interpolated string constant
private const string InterpolatedConstant = $"This is an interpolated constant {SomeConstant}";
public readonly override string ToString()
{
return $"{nameof(A)}: {A}, {nameof(B)}: {B}";
}
}
record DemoRecord(int A, string B)
{
//C# 10 - sealed ToString
public sealed override string ToString()
{
return $"{nameof(A)}: {A}, {nameof(B)}: {B}";
}
}
public readonly struct Measurement
{
//C# 10 - parameterless ctor in struct
public Measurement()
{
Value = double.NaN;
Description = "Undefined";
}
public Measurement(double value, string description)
{
Value = value;
Description = description;
}
//C# 9 (init only props)
public double Value { get; init; }
public string Description { get; init; }
public override string ToString() => $"{Value} ({Description})";
}
As you see, I packed several C#9 and 10 features into the code, and they compile happily. There were two tricky points, however. CallerArgumentExpression is not defined in .NET Framework; you have to add it to your program:
namespace System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
internal sealed class CallerArgumentExpressionAttribute : Attribute
{
public CallerArgumentExpressionAttribute(string parameterName)
{
ParameterName = parameterName;
}
public string ParameterName { get; }
}
The other problematic feature is init-only properties. To be able to compile your code to .NET 4.7, you have to define it yourself:
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit { }
}
For completeness, here is the project file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<LangVersion>10.0</LangVersion>
<ProjectGuid>{AFFA1FD0-4D9D-4801-B66C-26F232D32B3D}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>ConsoleApp1</RootNamespace>
<AssemblyName>ConsoleApp1</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CallerArgumentExpressionAttribute.cs" />
<Compile Include="IsExternalInit.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
I intentionally used the old project file format for this demo. It is useful to convert older projects to the new SDK format; however, it needs extensive testing, so we cannot schedule it for a while.
(For a nuget package, if the target is an SDK project or the older project with package references, nuget won’t copy nuget content to the destination project; it only links to the files. This design decision has many severe implications, but I don’t diverge more in this article.)
Here is the output of the running code:
It’s interesting to see how the CallerArgumentExpressionAttribute works. It nicely receives the argument C# expression.
To make sure I compiled for .NET 4.7, I decompiled the output assembly by DotPeak:
Could you hire me? Contact me if you like what I’ve done in this article and think I can create value for your company with my skills.