So, today I reflected an arbitary .NET assembly using ILSpy + dotPeek to gain deeper insight about how IL code works when I stumbled upon this weird part (dummy example):
public class SomeBaseClass {
public SomeBaseClass(SomeType[] iExpectACollection) {
...
}
}
public class SomeDerivedClass {
public SomeDerivedClass(SomeType onlyOneInstance) {
SomeType[] collection;
if(onlyOneInstance != null)
collection = new SomeType[] { onlyOneInstance };
base.\u002Ector(collection);
}
}
As far as I can see, the derived class doesn't call the base constructor in the first place, but instead does something with onlyOneInstance
and then calls be base constructor.
My question is: Is it possible to call the base constructor explicitly in C# after some work has been done? Or is this only possible in IL? I know it's being easily done in e.g. Java using super()
, however I've never seen it in .NET.
EDIT
I just had a talk with my boss and he's okay with posting some real world code of the library (it's one of our companys internal):
**IL PART**
.method public hidebysig specialname rtspecialname
instance void .ctor (
string contextId,
class MyComp.NetStack.BufferManager bufferManager,
class MyComp.NetStack.TcpChannelQuotas quotas,
class [System]System.Security.Cryptography.X509Certificates.X509Certificate2 clientCertificate,
class [System]System.Security.Cryptography.X509Certificates.X509Certificate2[] clientCertificateChain,
class [System]System.Security.Cryptography.X509Certificates.X509Certificate2 serverCertificate,
class MyComp.NetStack.EndpointDescription endpoint,
class MyComp.NetStack.ApplicationThreadPool threadPool
) cil managed
{
// Method begins at RVA 0x648e0
// Code size 263 (0x107)
.maxstack 10
.locals init (
[0] class MyComp.NetStack.EndpointDescription[]
)
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: ldarg.2
IL_0003: ldarg.3
IL_0004: ldarg.s serverCertificate
IL_0006: ldarg.s clientCertificateChain
IL_0008: ldarg.s endpoint
IL_000a: brtrue.s IL_000f
IL_000c: ldnull
IL_000d: br.s IL_0021
IL_000f: ldc.i4.1
IL_0010: newarr MyComp.NetStack.EndpointDescription
IL_0015: stloc.0
IL_0016: ldloc.0
IL_0017: ldc.i4.0
IL_0018: ldarg.s endpoint
IL_001a: stelem.ref
IL_001b: ldloc.0
IL_001c: newobj instance void MyComp.NetStack.EndpointDescriptionCollection::.ctor(class [mscorlib]System.Collections.Generic.IEnumerable`1<class MyComp.NetStack.EndpointDescription>)
Explicit Constructor Chaining using this() or super() Explicit use of the this() or super() keywords allows you to call a non-default constructor. To call a non-args default constructor or an overloaded constructor from within the same class, use the this() keyword.
A default constructor is one which takes no parameters, an implicit call is when you directly call the constructor.
You can do this:
public class SomeDerivedClass : SomeBaseClass {
public SomeDerivedClass(SomeType onlyOneInstance)
: base(new[] { onlyOneInstance})
{
}
}
In other words, you definitely can run code prior to the base constructor as part of constructing the parameters passed to it. In this case, we're constructing an array to pass to the base class. You can also call static methods, as recursive mentions.
I missed the null check. Apparently they want to preserve the null passing case rather than an array containing null. That would be the equivalent of :
public class SomeDerivedClass : SomeBaseClass {
public SomeDerivedClass(SomeType onlyOneInstance)
: base(onlyOneInstance != null ? new [] { onlyOneInstance} : null)
{
}
}
One way something like this might happen is field initializer logic.
Another way you can kind of achieve it is calling static methods for base constructor argument values.
class Base {
public Base(object value) {
Console.WriteLine ("Base constructor");
}
}
class Child : Base {
public Child() : base(DoWorkBeforeBaseConstructor()) { }
private static object DoWorkBeforeBaseConstructor() {
Console.WriteLine ("doing work");
return null;
}
}
Complementing the other answers:
The CLR allows you to run arbitrary stuff before calling the base class ctor. The CLR enforces that you do call the base class ctor in all possible paths through the method exactly once. I recently experimented with that using peverify
.
C# enforces other restrictions as you have noticed. If a different language was used to compile this assembly then only the CLR rules apply. And even those do not apply for SkipVerification
code which, I believe, is almost all code that runs nowadays.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With