Why do primary constructors in C# 12 do execute in opposite order?
That's kind of a breaking change to say the least...
Example:
namespace Whatever;
[TestClass]
public class UnitTestTemp
{
[TestMethod]
public void TestMethod1() // PASS // is expected, 1st is 1, 2nd is 2
{
using var stream = new MemoryStream(new byte[] { 1, 2, 3, 4 });
var classicDerived = new ClassicDerived(stream);
Console.WriteLine(classicDerived.Value1);
Console.WriteLine(classicDerived.Value2);
Assert.AreEqual(1, classicDerived.Value1);
Assert.AreEqual(2, classicDerived.Value2);
}
[TestMethod]
public void TestMethod2() // FAIL // is opposite, 1st is 2, 2nd is 1
{
using var stream = new MemoryStream(new byte[] { 1, 2, 3, 4 });
var primaryDerived = new PrimaryDerived(stream);
Console.WriteLine(primaryDerived.Value1);
Console.WriteLine(primaryDerived.Value2);
Assert.AreEqual(1, primaryDerived.Value1);
Assert.AreEqual(2, primaryDerived.Value2);
}
}
Classic constructor:
public class ClassicBase
{
public readonly int Value1;
protected ClassicBase(Stream stream)
{
Value1 = stream.ReadByte();
}
}
public class ClassicDerived : ClassicBase
{
public readonly int Value2;
public ClassicDerived(Stream stream) : base(stream)
{
Value2 = stream.ReadByte();
}
}
Primary constructor:
public class PrimaryBase(Stream stream)
{
public readonly int Value1 = stream.ReadByte();
}
public class PrimaryDerived(Stream stream) : PrimaryBase(stream)
{
public readonly int Value2 = stream.ReadByte();
}
1st test outcome:
TestMethod1
Source: UnitTestTemp.cs line 7
Duration: 4 ms
Standard Output:
1
2
2nd test outcome:
TestMethod2
Source: UnitTestTemp.cs line 21
Duration: 26 ms
Message:
Assert.AreEqual failed. Expected:<1>. Actual:<2>.
Stack Trace:
UnitTestTemp.TestMethod2() line 30
RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
Standard Output:
2
1
As you can see, it's a bit problematic if for instance you use a stream from within constructors.
Question:
Is there another way to address that beside reverting to classic constructors?
(was thinking of maybe something like SetsRequiredMembers for the new required modifier)
I think that you are comparing apples with oranges.
In the Classic classes you are initializing in the constructor, while in the Primary classes you are initializing in initializers. And the order of execution of constructors and initializers is indeed different. This has nothing to do with primary constructors.
You get the reverse order if you are using initializers without primary constructors being involved.
I have a sightly different setup. This is my helper class:
The classic class hierarchy:
The test
It prints:
See also: