This is the third part of the .NET Internals Cookbook series. For your convenience you can find other parts in the table of contents in Part 0 – Table of contents
Table of Contents
13. Can a variable be true and false at the same time?
Yes, see this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using System; public class Program { public static void Main() { Foo foo = new Foo(); if(foo && foo){ // Compiled to foo.false() ? foo.true() : Foo.&(foo, foo) Console.WriteLine("If"); } } } class Foo { public static bool operator true(Foo t) { Console.WriteLine("True"); return true; } public static bool operator false(Foo t) { Console.WriteLine("False"); return true; } public static Foo operator &(Foo lhs, Foo rhs) { Console.WriteLine("&"); return new Foo(); } } |
Output:
1 2 3 |
False True If |
The output is very misleading and that is because of operator rules.
We have a short circuiting in C#. This means that when we use logical conjunction (and) and first argument is false then second one is not evaluated. For logical disjunction (or) it happens when first argument is true.
To have the short circuiting in conjunction with custom operators we check whether the first argument is false. So we call foo.false()
here and based on that return value or carry on. In this sample we conclude that foo
is false (because the method returned true) so we want to “cast” it to boolean value (because this is what if condition requires). So we call foo.true()
. And then we enter the if block.
Decompiled code looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
IL_000a: call bool Foo::op_False(class Foo) IL_000f: brtrue.s IL_001a IL_0011: ldloc.2 IL_0012: ldloc.0 IL_0013: call class Foo Foo::op_BitwiseAnd(class Foo, class Foo) IL_0018: br.s IL_001b IL_001a: ldloc.2 IL_001b: call bool Foo::op_True(class Foo) IL_0020: stloc.1 .line 16707566,16707566 : 0,0 '' IL_0021: ldloc.1 IL_0022: brfalse.s IL_0031 .line 9,9 : 17,18 '' IL_0024: nop .line 10,10 : 4,28 '' IL_0025: ldstr "If" IL_002a: call void [mscorlib]System.Console::WriteLine(string) |
14. Can you create an object without invoking its constructor?
Yes, like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using System; using System.Runtime.Serialization; public class Program { public static void Main() { object o = FormatterServices.GetUninitializedObject(typeof(Foo)); Console.WriteLine(o); } } class Foo { Foo(){ Console.WriteLine("Constructor"); } } |
Output:
1 |
Foo |
This is useful when implementing some deserialization as we may want to initialize properties manually instead of invoking the constructor.
15. Can you invoke object’s constructor multiple times?
Yes, by using reflection. See this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using System; public class Program { public static void Main() { Foo foo = new Foo(); foo.GetType().GetConstructor(new Type[0]).Invoke(foo, new object[0]); foo.GetType().GetConstructor(new Type[0]).Invoke(foo, new object[0]); foo.GetType().GetConstructor(new Type[0]).Invoke(foo, new object[0]); } } class Foo { public Foo(){ Console.WriteLine("Constructor!"); } } |
Output (no surprise):
1 2 3 4 |
Constructor! Constructor! Constructor! Constructor! |
You can also generate some IL code doing that.
16. Can you return null
from a constructor?
Yes, try this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
using System; using System.Runtime.Remoting.Proxies; namespace ConsoleApp { class Program { public static void Main() { var instance = new Foo(); Console.WriteLine(instance == null); } } class FooProxyAttribute : ProxyAttribute { public override MarshalByRefObject CreateInstance(Type serverType) { Console.WriteLine("Custom new"); return null; } } [FooProxy] class Foo : ContextBoundObject { } } |
It gets even more interesting if you try it here — it runs custom constructor but the reference is still non-null. I tried it with VS and get the expected result, can’t explain what is happening in tio.
17. Can you create an instance of an interface?
Yes, see this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
using System; using System.Runtime.InteropServices; public class Program { public static void Main() { IFoo obj = new IFoo("abc"); Console.WriteLine(obj.Message); } } class Foo : IFoo { readonly string name; public Foo(string name) { this.name = name; } string IFoo.Message { get { return "Hello from " + name; } } } // these attributes make it work // (the guid is purely random) [ComImport, CoClass(typeof(Foo))] [Guid("d60908eb-fd5a-4d3c-9392-8646fcd1edce")] interface IFoo { string Message {get;} } |
Wow, magic. We not only create instance of an interface but also pass some constructor arguments! When you decompile the code you can see that actually under the hood you have this:
1 2 3 |
IL_0001: ldstr "abc" IL_0006: newobj instance void Foo::.ctor(string) IL_000b: stloc.0 |
So we just create an instance of Foo
class. This is because we actually create a COM object under the hood.