This is the fourth 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
18. Can you add type constructor to the interface?
In C# no. In IL — yes. See the code below:
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Metadata version: v4.0.30319
.assembly externmscorlib
{
.publickeytoken=(B77A5C561934E089)// .z\V.4..
.ver4:0:0:0
}
.assembly'51e89a1e-6120-4a32-a6f0-bbbe95846cbc'
{
.hash algorithm0x00008004
.ver0:0:0:0
}
.module'51e89a1e-6120-4a32-a6f0-bbbe95846cbc.dll'
// MVID: {DC9AB443-034C-4EFA-9D7C-D8DE9E14DA85}
.imagebase0x10000000
.file alignment0x00000200
.stackreserve0x00100000
.subsystem0x0003// WINDOWS_CUI
.corflags0x00000001// ILONLY
// Image base: 0x01030000
// =============== CLASS MEMBERS DECLARATION ===================
We have an interface here and a method named cctor. There are special names in IL, constructors are named ctor whereas type constructors (or type initializers) are considered a class constructors hence the name cctor.
However, it is not very useful as the constructor is not called. You can specify type constructor for structs as well, but it may be ignored in some situations. For instance, “The creation of default values (§18.3.4) of struct types does not trigger the static constructor” (from the C# standard).
Why would you do that? If you use language without direct support for indexers (you do remember there are over 20 languages running on CLR, don’t you?), you need to call them by name.
20. Can you have static fields and methods in the interface?
Yes, you can. 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// Metadata version: v4.0.30319
.assembly externmscorlib
{
.publickeytoken=(B77A5C561934E089)// .z\V.4..
.ver4:0:0:0
}
.assembly'cced1cd5-fc6a-40bb-934a-8a8958e42648'
{
.hash algorithm0x00008004
.ver0:0:0:0
}
.module'cced1cd5-fc6a-40bb-934a-8a8958e42648.dll'
// MVID: {65C0D9A7-200B-4DE7-9B06-E490729C037F}
.imagebase0x10000000
.file alignment0x00000200
.stackreserve0x00100000
.subsystem0x0003// WINDOWS_CUI
.corflags0x00000001// ILONLY
// Image base: 0x01260000
// =============== CLASS MEMBERS DECLARATION ===================
22. What is covariance? Contravariance? How does it work with arrays and with generics?
This allows you to return or accept subtype when base type is expected. This short explanation actually has a lot of intricate details which you may have just ignored.
First, type and class are not the same. Type specifies requirements imposed on an object whereas class is a concrete implementation of those. In C# we have classes only but in other languages you can have types without classes or object-oriented programming without classes at all. Oddly enough, JS is not considered an object-oriented language even though it is based on objects everywhere whereas C# is considered OOP but it is based on classes (maybe we should call it “class-oriented language”? Let the flamewar begin!).
This also means that we can have a subtype and not a subclass at the same time or a subclass which is not a subtype! Technically we can consider nominal subtyping, structural subtyping, subtyping without subclassing and many other combinations. I highly recommend reading A Theory of Objects which explains it in depth. Also, playing with JS or Scala can be beneficial as those languages have very interesting type systems.
Let’s get back to C#. Imagine there is a class Base and a class Derived : Base. We can consider IEnumerable< Derived> being an instance of IEnumerable< Base> because Derived is a subtype of Base. Thanks to that we can assign IEnumerable< Derived> to IEnumerable< Base> and it works. Typically we can use it when returning values from methods — we often say that “return value” is in a “covariant position” which is true for most of the times.
The same goes for input parameters: we can create a delegate of type Action< Base> and assign to it an instance of Action< Derived> and it does work. We say that “input parameter” is in a “contravariant position” (once again, true for most of the times).
Covariance is supported in arrays, delegates (also called “method group variance”) and generic type parameters in interfaces and delegates. It is marked using out keyword in C#. Contravariance is supported in delegates and generic type parameters in interfaces and delegates. It is marked with in keyword.
Covariance in arrays is dangerous. It is supported because Java had it before C# emerged and then C# just copied it. But it can easily break your application:
1
2
3
4
5
6
7
8
9
10
using System;
publicclassProgram
{
publicstaticvoidMain()
{
object[]objects=newstring[10];
objects[1]=5;
}
}
Output:
1
2
3
4
5
6
Run-time exception(line-1):Attempted toaccess an element asatype incompatible with the array.
Stack Trace:
[System.ArrayTypeMismatchException:Attempted toaccess an element asatype incompatible with the array.]
at Program.Main()
You use an array of strings as an array of objects. This is fine unless you assign a non-string to its content. If you do — you get an exception in runtime. Fortunately, most of the times compiler can find those subtle bugs when we use generic types.
We have a lambda accepting strings and printing them out and then we want to use it as a lambda accepting objects. This clearly can’t work as we would be allowed to pass object as a string parameter. So generally input position is contravariant whereas output position is covariant.
This is because extension methods are compiled as static ones. This code in fact does that:
1
IL_0004:call voidFoo::Extend(int32)
In C# you could write it as:
1
Foo.Extend(x);
However, DLR looks for methods of the object so you cannot use extensions with dynamic code.
As a side note, this actually explains why creating traits using just extension methods is not very useful (unless we implement polymorphism manually. That’s because when you call a method the call is bound statically so you have no dynamic dispatch which is the whole point of trait mechanism.
Yet another side note, Java introduced default methods in interfaces which can be used to build very basic traits. They do support polymorphism (so they are dynamically bound) but you cannot call base method (super in Java nomenclature) out of the box, you need to specify the superclass (base implementation) explicitly. Hopefully C# gets this right (or we can always implement polymorphism manually).
24. What is the difference between Equals and ==?
Equals is bound in runtime, == is bound in compile time. 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
32
33
34
35
36
37
38
using System;
publicclassProgram
{
publicstaticvoidMain()
{
Foo one=newFoo();
Foo two=newFoo();
Console.WriteLine("Foo one AND Foo two");
boola=one.Equals(two);
boolb=one==two;
Console.WriteLine("Foo one AND object three (which is still Foo)");
objectthree=two;
boolc=one.Equals(three);
boold=one==three;
Console.WriteLine("object three (which is still Foo) AND Foo two");
boole=three.Equals(one);
boolf=three==one;
}
}
classFoo
{
publicoverride boolEquals(objectthat){
Console.WriteLine("Custom Equals");
returnbase.Equals(that);
}
publicstaticbooloperator==(Foo lhs,Foo rhs){
Console.WriteLine("Custom ==");
returnobject.ReferenceEquals(lhs,rhs);
}
publicstaticbooloperator!=(Foo lhs,Foo rhs){
Console.WriteLine("Custom !=");
return!object.ReferenceEquals(lhs,rhs);
}
}
Output:
1
2
3
4
5
6
7
Foo one ANDFoo two
Custom Equals
Custom==
Foo one ANDobjectthree(which isstill Foo)
Custom Equals
objectthree(which isstill Foo)ANDFoo two
Custom Equals
You can see that Equals uses actual object types whereas == is bound statically. If you decompile the code you get:
26. What is the difference between new Struct() and default(Struct)?
The former calls parameterless constructor, the latter creates new instance and with all fields zeroed out. Most likely this doesn’t make a difference for you.