Sei sulla pagina 1di 23

Principles of Program Analysis

Generics in C# - The good, the bad & the ugly


Author Supervisors Technical Adviser
Lukas Kretschmar Prof. Dr. Josef M. Joller Thomas Corbat
lukas.kretschmar@hsr.ch Prof. Peter Sommerlad
University of Applied Sciences Rapperswil
Seminar Report, June 5, 2015

Abstract
Generics - some like them, some hate them, some have mastered their usage, some get ambushed
by compiler errors and some just dont use them for whatever reason. Even though the concept of
Generics, and in a broader view Generic Programming, is out there some years, even decades, there
exist still developers that have never heard of Generics or struggle with Type Parameters.
In this paper we provide a broad summary on Generics in C#. At first, we present their current
capabilities. These are the parts that are mostly mentioned in books giving an introduction on the
programming language.
Based on the known capabilities, we show what Generics cant do, respectively where the limitations
are. And at the end, we go one step further, presenting solutions to circumvent some limitations,
mention unexpected pitfalls and other ugly parts. They are normally not mentioned when Generics
are introduced and are the main reason why some cant stand Generics.

Keywords: Generics, Parametrized Types, Generic Programming, C#, Java, C++

1
1 INTRODUCTION

Contents often than others, some too many times than pre-
ferred. There are many root causes for the vio-
1 Introduction 2 lation of the DRY (dont repeat yourself) princi-
1.1 Related work . . . . . . . . . . . . 2 ple [36]. But most likely, it is one of the following:
1.2 Overview . . . . . . . . . . . . . . 3
Copy & paste
2 Generic Programming and Parame-
terized Types 3 Very similar functionality

3 The Good 3 Generated code


3.1 Implementation of Generics . . . . 3
3.2 Type Constraints . . . . . . . . . . 5 The latter is sometimes not avoidable due to the
3.2.1 class & struct . . . . . . . . 5 lack of configurability of the generator but in a
3.2.2 Inheritance & Interfaces . . 7 way not that bad since it is managed through the
3.2.3 new() . . . . . . . . . . . . 7 generator and could be easily regenerated.
3.3 Co- & Contravariance . . . . . . . 8 Similar functionality is maybe not seen by the
3.3.1 Covariance . . . . . . . . . 8 developer in the first place, and needs to be refac-
3.3.2 Contravariance . . . . . . . 8 tored to a point where the code duplication is ob-
vious. And copy & paste duplication is obviously
4 The Bad 9 duplicated.
4.1 Generic Properties . . . . . . . . . 9 Anyway, chances are high if we encounter du-
4.2 Type Constraints . . . . . . . . . . 9 plicated code, only the used types will differ. And
4.3 Unusable superclasses . . . . . . . 10 if this is the case, using some sort of Generic Pro-
4.3.1 Enums . . . . . . . . . . . . 10 gramming [43], in our case Generics, is the best
4.3.2 Nullable Types . . . . . . . 10 way to go. Generics are a concrete application of
Parameterized Types, or subset of Generic Pro-
4.3.3 new() . . . . . . . . . . . . 11
gramming, in C#, Java and other programming
4.4 Co- & Contravariance . . . . . . . 12
languages.
4.4.1 Variance when defined . . . 12
4.4.2 Variance only on interfaces 13
1.1 Related work
5 The Ugly 13
5.1 Generics and static . . . . . . . . . 13 The idea of Parameterized Types is out there for
5.2 Operator overloading . . . . . . . . 15 years and has its origins in a paper published in
5.2.1 Operator inheritance . . . . 16 1989 by Musser and Stepanov defining the term
5.2.2 Using dynamic . . . . . . . 17 Generic Programming [43]. A refined paper was
5.3 Stacking Type Parameters . . . . . 18 then published later on [27].
Regarding Generics in C#, some publications
6 Conclusion 18 were done by Kennedy et al ( [39], [40], [38],
[41], [37]) of Microsoft Research Cambridge [17].
A Domain model 23 While these papers focus on concrete capabilities
and implementations, there exist some more for-
mal definitions how Generics in C# can be speci-
fied ( [48], [29]).
There has also been done some work by Gar-
cia et al [33], comparing the different approaches
1 Introduction how Parameterized Types are supported in C#,
Java, and C++. But this was done before C# and
Every software engineer stumbles from time to Java officially supported Generics. A newer com-
time over duplicated code, probably the most parison of the different implementations on Pa-
common code smell [30] out there. Some more rameterized Types was done by Giovannelli [35].

2
1.2 Overview in C++. In this case, it is not necessary that
an interface must be declared (as we are used
In this paper we are going to have a look at
to if using C# or Java) and shared among types.
Generics in C# [4], their implementation and
Hence types are not forced to have a common
their capabilities especially focusing on the limi-
supertype or implement the same interface. A
tations and unsound properties. Where appropri-
type that is used with the Generic code just has
ate, we will compare the situation with Generics
to offer the expected capabilities.
in Java [5] or Templates in C++ [3].
Explicitly requires that the interface must be
We know, Templates are not directly compara- declared for the given code at first, as it is the
ble with Generics. But since both concepts (not case in C# and Java. And there are also some
to be confused with C++ -concepts [28]) are ap- developments in C++ to introduce explicit inter-
plications of Parameterized Types in their lan- face requirements ( [28], [47]).
guages we find it appropriate to mention their Parameterized Types are types that use
commonalities and differences. placeholders for types, called Type Parameters,
when defined. The specific types of the place-
The paper is structured as follows: In section
holders are unknown until the Parameterized
2, we give a brief introduction on Generic Pro-
Type is used. With respect to the languages used
gramming. In section 3, we have a look at the
in this paper, we define Parameterized Types
implementation of Parameterized Types in the
with
CLI and mention the capabilities of Generics in
C<T [, U ] >
C#. Their limitations are mentioned in section
4, followed by possible pitfalls in section 5. And where C denotes the class, T the first Type Pa-
finally, in section 6, we reflect on our findings. rameter and [, U ] any additional Type Parame-
Since the capabilities of Generics are normally ter.
covered by books on C# (e.g. [46] or [26]), we fo-
cus on sections 4 and 5.
An overview of the used classes, in terms of 3 The Good
their dependencies, is attached to appendix A.
We take the liberty to simplify code examples In this section, we examine the aspects of Gener-
and omit unnecessary details (e.g. if a modifier ics in C# presenting a real benefit for the devel-
is missing, assume public, with the exception of oper.
fields, where we assume private).
3.1 Implementation of Generics
2 Generic Programming and To really see the power of Generics in C# , we will
compare the implementation on a more detailed
Parameterized Types and deeper level. But lets start with three differ-
ent definitions of a Parameterized Type Cup<T >
Generic Programming [43] paraphrases the in C# (figure 1), Java (figure 2) and C++ (figure
ability to write code in a way it is usable with 3).
different types that just provide at least the in- Even though the definitions and creations look
terface the code expects. How this interface is extremely similar to each other, the compiled re-
guaranteed may vary among the different used sult of the three examples are very different.
approaches to support Generic Programming.
We differentiate between two possibilities: Compiled Generics in Java
The simplest integration (regarding the three
Implicitly
mentioned languages) of Parameterized Types
Explicitly into the language and their execution environ-
ment, in this case the JVM, is done by Java. Java
Implicitly means the interface is derived by the uses a technique called Type Erasure ( [45], [44]).
used capabilities within the code as it is the case Simply explained, this means throwing away the

3
3 THE GOOD

1 // D e f i n i t i o n 1 // D e f i n i t i o n
2 c l a s s Cup<T> 2 template<typename T>
3 { 3 c l a s s Cup {
4 T content ; 4 T content ;
5 } 5 }
6 6
7 // A p p l i c a t i o n 7 // A p p l i c a t i o n
8 var cupCake 8 Cup<Cake> cupCake ;
9 = new Cup<Cake > ( ) ; 9 Cup<C o f f e e > c u p C o f f e e ;
10 var c u p C o f f e e
11 = new Cup<C o f f e e > ( ) ; Figure 3: C++: Cup<T >

Figure 1: C#: Cup<T >


1 // D e f i n i t i o n
2 public c l a s s Cup {
1 // D e f i n i t i o n 3 private j a v a . l a n g . Object c o n t e n t ;
2 c l a s s Cup<T> { 4 }
3 T content ; 5
4 } 6 // A p p l i c a t i o n
5 7 new Cup
6 // A p p l i c a t i o n 8 i n v o k e s p e c i a l Cup ( )
7 Cup<Cake> cupCake 9 a s t o r e 1 [ cupCake ]
8 = new Cup<Cake > ( ) ; 10 new Cup
9 Cup<C o f f e e > c u p C o f f e e 11 i n v o k e s p e c i a l Cup ( )
10 = new Cup<C o f f e e > ( ) ; 12 astore 2 [ cupCoffee ]

Figure 2: Java: Cup<T > Figure 4: Resulting Java Bytecode of Cup<T >.

Type Parameters during compilation. The Type


dling Parameterized Types, we now turn to C#.
Parameters are only used during compilation to
If code in C# is compiled, it gets compiled into
ensure the code is type-safe.
the so called Intermediate Language (further re-
If we take the example of figure 2, the com-
ferred to as IL). This is a language used by the
piled code will look like figure 4. We notice that
CLR (Common Language Runtime) [9], a virtual
Cup is no longer a Generic Type (line 2) and the
machine.
occurrence of Type Parameter T was replaced by
type Object (line 3). Despite the other two examples, where the
Type Parameters information gets either thrown
Compiled Templates in C++ away or directly compiled into the type, the IL
C++, compared to Java, applies the opposite ap- actually supports Parameterized Types directly
proach. Rather than throwing away information, [39]. This is possible since the CLR is able to
the templates are separately compiled for every handle Parameterized Types ( [1], [2]).
combination of Type Parameters. Taking the ex- If we compile the code in figure 1, the IL-code
ample from figure 3, the compiled code will look will look like figure 6. We immediately notice
like figure 5. As we can see, now there exist two that there is only one definition of Cup0 1<T >
classes of Cup<T >, Cup<Cof f ee> (line 2) and with a Type Parameter (line 2) and the field us-
Cup<Cake> (line 2). ing it (line 4).
This integration of Generics into the virtual
Compiled Generics in C# machine provides certain advantages. Compared
Having seen the most extreme approaches of han- to Java, it is possible to use Value Types [23] as

4
3.2 Type Constraints

1 // D e f i n i t i o n 1 // D e f i n i t i o n
2 . c l a s s Cup<C o f f e e > { } 2 . c l a s s public Cup1<T>
3 . c l a s s Cup<Cake> { } 3 {
4 4 . f i e l d private ! T content
5 // A p p l i c a t i o n 5 }
6 . locals 6
7 ( [ 1 ] v a l u e t y p e Cup<Cake> 7 // A p p l i c a t i o n
8 cupCake , 8 . locals init
9 [ 2 ] v a l u e t y p e Cup<C o f f e e > 9 ( [ 0 ] c l a s s Cup1< c l a s s Cake>
10 cupCoffee ) 10 cupCake ,
11 [ 1 ] c l a s s Cup1< c l a s s C o f f e e >
Figure 5: Disassembled template Cup<T > in 12 cupCoffee )
C++. To easily compare the results, we compiled 13 newobj
our C++ -code to IL-code. 14 c l a s s Cup1< c l a s s Cake > : : . c t o r ( )
15 stloc .0
16 newobj
Generics without boxing [8] resulting in better 17 c l a s s Cup1< c l a s s C o f f e e > : : . c t o r ( )
performance and memory usage. Compared to 18 stloc .1
C++, the explosion of code compiled into a library
is limited to the necessary parts. The CLR will Figure 6: Disassembled class Cup<T > in IL-
expand and store a copy of a Generic Type when code.
it is needed during runtime. As a result, we can
compare Generic Types and they will only match
if the Type Parameters match as well (lines 2 to C# allows five different constraints:
7, figure 7).
class | struct
Thus the behavior of Generic Types in C# is
pretty similar to C++s templates but without the inheritance of class (or struct)
compilation overhead. But it is still possible to
access the raw Generic Type (line 10, figure 7). implementation of interface(s)
Another consequence, of the CLR still knowing
the Type Parameters, is the possible usage of the is subtype of other Type Parameter
Type Parameters in static context. For exam- existence of parameterless constructor
ple, a static field using a Type Parameter is com-
pletely valid (figure 8). It should be obvious that It is possible to leave mentioned restrictions, but
this is not possible in Java. Since static fields are if restrictions on Type Parameters are defined,
shared between different instances and Javas us- the order (as it is defined above) of restrictions
age of Type Erasure, it cant be guaranteed that has to be taken into account.
the Type Parameter T will be the same in every
instance. 3.2.1 class & struct
But static fields in Generic Types (at least in
C#) introduce some pitfalls a developer has to be The first possible restriction on a Type Param-
aware of. We will discuss them in section 5.1. eter is its basic structure. We can distinguish
between class and struct. class is in this case
maybe a bit misleading as it simply forces the
3.2 Type Constraints Type Parameter to be a Reference Type [22] and
this also includes interfaces.
Type Constraints are used to restrict Type Pa-
rameters respectively guarantee the implemen- Using class means:
tation of interfaces, inheritance of a class (or Type Parameter is nullable: Since the
struct) or some more C#-specific capabilities [10]. Type Parameter is a Reference Type, any field

5
3 THE GOOD

1 // t r u e 1 public T Convert<T, V>(V v a l u e )


2 typeof (Cup<Cake>) 2 where T : c l a s s
3 == typeof (Cup<Cake >); 3 where V : c l a s s
4 4 {
5 // f a l s e 5 // v a l i d
6 typeof (Cup<Cake>) 6 bool i s N u l l = v a l u e == null ;
7 == typeof (Cup<C o f f e e >); 7
8 8 // P o s s i b l e
9 // t r u e 9 // N u l l R e f e r e n c e E x c e p t i o n
10 typeof (Cup<Cake>) 10 bool eq =
11 . GetGenericTypeDefinition () 11 v a l u e . Equals ( otherValue ) ;
12 == typeof (Cup<C o f f e e >) 12
13 . GetGenericTypeDefinition ( ) ; 13 return v a l u e as T ; // v a l i d
14 }
Figure 7: Comparing Genetic Types in C#.
Figure 9: Example of class constraint.
1 // D e f i n i t i o n
2 c l a s s Cup<T> null unnecessary respectively not possible since
3 { the == operator is not guaranteed to be
4 static T content ; defined. As a consequence, we always have
5 } to compare values with the Equals() method
6 since this is always possible (figure 10). We will
7 // A p p l i c a t i o n provide a workaround for the missing operators
8 Cup<Cake >. c o n t e n t = new Cake ( ) ; in section 5.2.
9 Cake cake = Cup<Cake >. c o n t e n t ;
1 public T Convert<T, V>(V v a l u e )
Figure 8: A Type Parameter used in a 2 where T : struct
static field. 3 where V : struct
4 {
5 // i n v a l i d , == not d e f i n e d
or argument of the given Type Parameter might 6 bool i s N u l l = v a l u e == null ;
be null . On the other hand, comparing against 7
null is possible respectively must be considered 8 // v a l i d , v a l u e cannot be n u l l
(figure 9). 9 bool eq =
Casts with as are allowed : As a logi- 10 v a l u e . Equals ( o t h e r V a l u e ) ;
cal consequence of forcing the Type Parame- 11
ter to be a Reference Type, we can use it as 12 // T cannot be n u l l
type casting with as [7]. as is a safe cast in 13 // hence c a s t w i t h as i s i n v a l i d
C# returning either an instance of the targeted 14 return v a l u e as T ;
type or null (figure 9). 15 }
On the other side, we can restrict the Type Pa-
rameter to be a struct. In this case, only Value Figure 10: Example of struct constraint.
Types [23] are allowed.
Using struct means: As good as these restrictions to class and
Type Parameter must be non-nullable: struct may sound, in C# it is possible to declare
Since only Value Types are allowed we know any nullable Value Types and they dont fit any of
field or argument of the given Type Parameter both restrictions. We will discuss this issue in
cannot be null. This makes any check against section 4.3.2 in more detail.

6
3.2 Type Constraints

default(T) 1 c l a s s Machine<T>
Another consequence of the distinction between 2 where T : I B e v e r a g e
class and struct is that we cant determine the
default value of a given Type Parameter. If we
restrict to class, it is null . This is the same as Figure 12: Constraining M achine<T > to allow
in Java. But if there is struct or no constraint only beverages.
at all, its no longer possible to just assume that
null works as a default value. In this case, we
can use the default keyword. Applied with the based on the Type Parameter. To do so, we can
Type Parameter the default value of the given extend the Type Parameters constraint with the
Type Parameter is always returned. keyword new() requiring the Type Parameter to
Another possible approach would be using the provide a public parameterless constructor. If we
new() restriction described in section 3.2.3. But add this restriction to our M achine<T >, we can
this seems to be more of an ugly workaround to now create new instances of our given Type Pa-
get a valid value. rameter (line 6, figure 13).

3.2.2 Inheritance & Interfaces


Lets consider a M achine<T > as shown in fig- 1 c l a s s Machine<T>
ure 11. Such a M achine<T > could fill every- 2 where T : IBeverage , new( )
thing into a container (e.g. Cup<T >) as long 3 {
it implements IF illable<T >. As interesting as 4 void F i l l ( I F i l l a b l e <T> cup )
5 {
6 var b e v e r a g e = new T ( ) ;
1 c l a s s Machine<T> 7 cup . F i l l W i t h ( b e v e r a g e ) ;
2 { 8 }
3 void F i l l ( I F i l l a b l e <T> cup ) 9 }
4 { ...
5 }
6 } Figure 13: A M achine<T > that can create
whatever beverage it is used for.

Figure 11: M achine<T >

Such a capability with Type Parameters is cur-


a M achine<Cake> might sound we only want
rently not possible in Java since, as we mentioned
a machine that can fill a Cup<T > with some-
in section 3.1, the Type Parameters information
thing to drink. To achieve this goal, we can
is gone after the code is compiled. There exists a
add a Type Bound on T to implement the in-
possibility to create instances of given Type Pa-
terface IBeverage. Thus line 1 in figure 11 will
rameters using Class<T > but this is not in the
be extended to allow only something implement-
scope of this paper.
ing IBeverage (figure 12) and our Cake-Machine
is history. C++, on the other side, provides the same pos-
Besides interfaces, we could also force inher- sibility. Since the compiler basically replaces T
itance of a certain class or even another Type with the type given by the Type Parameter, it
Parameter (e.g. T : U ). just has to provide an empty constructor that
is accessible within the template and its fine.
3.2.3 new() C++ goes even further. As it is just checked if
the expected signature exists, it is also possible
As mentioned in the previous section, Type Pa- to force constructors with parameters. We will
rameter information still exists in the IL-code. discuss this missing feature in C# later in section
Due to this, it is possible to create a new instance 4.2.

7
3 THE GOOD

3.3 Co- & Contravariance 1 IEmptyable<Cake> cupCake


With the introduction of Generics some new 2 = new Cup<Cake > ( ) ;
problems and pitfalls were also introduced. One 3 IEmptyable<IFood> f o o d
special case is the behavior involving subtyping. 4 = new Cup<Cake > ( ) ;
More details on the topic of subtyping can be 5 IEmptyable<Tea> teaCup
found in another seminar paper by Gasser [34]. 6 = new Cup<IceTea > ( ) ;
We will now have a look on the behavior when
Type Parameters are used in covariant and con- Figure 16: Application of covariant
travariant positions. Restrictions regarding vari- IEmptyable<T > interface.
ance will be discussed in section 4.4. In the fol-
lowing subsections we will only show how it is
used. 1 i n t e r f a c e I C o n t a i n e r <out T>
2 {
3 T Content { g e t ; }
3.3.1 Covariance 4 }
Using Type Parameters in covariant positions
means using them only as return types. To define Figure 17: Covariant Property
a Type Parameter that is only used in covariant
positions, we can introduce the out keyword in
the interface declaration as shown in figure 14.
3.3.2 Contravariance

1 i n t e r f a c e IEmptyable<out T> Type Parameters in contravariant positions are


2 { types of input parameters. To define that we will
3 T Empty ( ) ; only use Type Parameters on input parameters,
4 } we can use the in keyword as shown in figure 18.

Figure 14: Covariant IEmptyable<T > inter-


face. out denotes T will only be used in covariant
positions, meaning only as return type. 1 i n t e r f a c e I F i l l a b l e <in T>
2 {
3 void F i l l W i t h (T c o n t e n t ) ;
Now, if we assume we have a class Cup<T > 4 }
(figure 15) defined, we can assign instances of
Cup<T > to more generic interfaces of type
IEmptyable<T > as shown in figure 16. Figure 18: Contravariant IF illable<T > inter-
face. in denotes T will only be used in contravari-
ant positions, meaning only as input parameter
1 c l a s s Cup<T> type.
2 : IEmptyable<T>

Figure 15: A Cup<T > implementing interface If we again assume we have a class Cup<T >
IEmptyable<T >. (figure 19) implementing IF illable<T >, we can
now fill it with any matching subtype as shown
Regarding covariance, the above defined usage in figure 20.
also applies to Properties. If we define a Type Again, using Type Parameters in Properties, in
Parameter only used in covariant positions, it is this case in a contravariant manner, is possible,
only applicable on getters as shown in figure 17. as long as we only define setters. Figure 21 shows
how it is done.

8
4.2 Type Constraints

1 c l a s s Cup<T> 1 i n t e r f a c e I C o n t a i n e r <in T>


2 : I F i l l a b l e <T> 2 {
3 T Content { s e t ; }
Figure 19: A Cup<T > implementing interface 4 }
IF illable<T >.
Figure 21: Contravariant Property
1 I F i l l a b l e <Tea> teaCup
2 = new Cup<Tea > ( ) ; 1 class Container
3 teaCup . F i l l W i t h (new Tea ( ) ) ; 2 {
4 I F i l l a b l e <IceTea> iceTeaCup 3 T Content<T> { g e t ; s e t ; }
5 = new Cup<Tea > ( ) ; 4 }
6 iceTeaCup . F i l l W i t h (new IceTea ( ) ) ;
Figure 22: Property with Type Parameter.
Figure 20: Application of contravariant
IF illable<T > interface.
we notice that the compiler already raises errors
since get Value() and set Value() are already re-
4 The Bad served by the class (the error occurs on line 3,
In this section we discuss the limitations of C#s figure 23). For the sake of simplicity, we assume
implementation of Generics. the class is compilable as it is and the resulting
IL-code would be in the same class as well.

4.1 Generic Properties


1 class Container
We mentioned in a previous section that Generics 2 {
can also be used with Properties. And we have 3 int Value { g e t ; s e t ; }
seen how Generics are used on methods. Since 4 int get Value ( ) { . . . }
Properties are just syntactic sugar for getter and 5 void set Value ( int v a l u e ) { . . . }
setter methods the question might come up if it 6 }
is possible to use Type Parameters in Property
definitions as shown in figure 22.
Figure 23: Class used to compare Properties
But Generic Properties are not possible. The
and methods.
reason lies in the CLR. The compiler cannot
know how much space to allocate for storing an
instance (in our case a Container ) when it is cre- As we see in figure 24, the differences are min-
ated, since T is only known when the Property is imal. Getter and setter methods of Properties
accessed [15]. only add a specialname token (compare lines 5
Now, there may be the followup question: Why and 10 respectively lines 16 and 21). The re-
is this still prohibited when the Type Parameter maining code is equal.
is not used as type of the backing field? Thus, we Hence, Generic Properties are basically not
limit the used scope of the Type Parameter to the possible because the compiler doesnt support
Propertys body and the compiler doesnt have them.
to worry when allocating space on the heap. The
question is legit since we said before that Proper-
ties are just another way of providing getter and 4.2 Type Constraints
setter methods.
Lets try to find the difference in the IL-code. Regarding Type Constraints, even if they are
If we take a class as shown in figure 23 the IL- much more flexible than the capabilities provided
code would look like figure 24. When compiling, in Java, there exist some limitations.

9
4 THE BAD

1 . c l a s s public C o n t a i n e r For the other not supported classes, there is


2 { no hard evidence why they are not allowed. Re-
3 // P r o p e r t y g e t t e r garding System.Enum, there is exists a statement
4 . method private from Lippert (member of the C# language design
5 specialname int32 team).
6 get Value ( ) { . . . } ALL features are unimplemented until some-
7
one designs, specs, implements, tests, documents
8 // Method get Value and ships the feature. So far, no one has done
9 . method private that for this one. Theres no particularly unusual
10 int32 reason why not; we have lots of other things to
11 get Value ( ) { . . . } do, limited budgets, and this one has never made
12
it past the wouldnt this be nice? discussion in
13
the language design team. [13]
14 // P r o p e r t y s e t t e r We assume this statement is also valid for the
15 . method private other types in the list.
16 s p e c i a l n a m e void
17 set Value ( i n t 3 2 v alue ) { . . . } 4.3.1 Enums
18
19 // Method s e t V a l u e Even though we can constrain Type Parameters
20 . method private to be a class or struct (see section 3.2.1), it is
21 void not possible to force a Type Parameter to be an
22 set Value ( i n t 3 2 v alue ) { . . . } enum. According to [11], forcing an enum is
23 } possible if directly written in IL. But, as men-
tioned before, its simply not supported by the
compiler as it isnt that important for the devel-
Figure 24: IL-code of compiled Container to opment team.
compare Properties and methods. Unnecessary The closest possible constraint in C# for
details are omitted. enum types is to use struct since enums are
Value Types [2].
4.3 Unusable superclasses
4.3.2 Nullable Types
If we want to constraint our Type Parameter to
inherit from a specific superclass we cannot use Using the keyword struct as Type Constraint
the following five classes: for Value Types is a bit misleading since not all
structs are allowed. C# knows a construct called
System.Array N ullable<T > (short T ?) [16] allowing to wrap
Value Types and assign null. If we have a look
System.Delegate

System.Enum 1 // i n v a l i d
2 int i = null ;
System.ValueType 3
4 // v a l i d
object 5 N u l l a b l e <int> j = null ;
6 int ? k = null ;
While these types exist in the framework, it is not
possible to use them for inheritance since they
are attached implicitly. Furthermore, constrain- Figure 25: Assigning null to Nullable Types.
ing to object is useless since every Type Param-
eter T fulfills this constraint as long as no other at the implementation we see that N ullable<T >
constraint is more specific. is in fact implemented as a struct making it a

10
4.3 Unusable superclasses

Value Type. But on the other hand, null is a 1 // v a l i d


valid value. 2 Use (new object ( ) ) ;
Regarding Type Constraints for Type Param- 3
eters, this is pretty confusing and in fact as soon 4 // i n v a l i d
as a Type Constraint of either class or struct is 5 // i m p l e m e n t a t i o n on l i n e 1
present, it is not possible to use a Nullable 6 // o f f i g u r e 26 i s used
Type. Since class expects a Reference Type, 7 Use<int > ( 4 2 ) ;
what N ullable<T > is not, and struct expects a 8
non-nullable Value Type, what is N ullable<T > 9 // v a l i d
either, non of these constraints are applicable. 10 Use ( ( int ? ) 4 2 ) ;
The only possible option, when Nullable Types 11
have to be allowed, with one of the two con- 12 // v a l i d
straints is providing two separate implementa- 13 int ? theAnswer = 4 2 ;
tions applying the restrictions separated from 14 Use ( theAnswer ) ;
each other. If this scenario occurs on a method
its not that hard to solve. Since T ? changes the
Figure 27: Usage of overloaded methods with
Nullable Types.
1 void Use<T>(T item )
2 where T : c l a s s { . . . }
3
1 T Create<T>()
4 void Use<T>(T? item ) 2 where T : new( )
5 where T : struct { . . . } 3 {
4 return new T ( ) ;
5 }
Figure 26: Overload with Nullable Type.

Figure 28: Example of using new() constraint.


method signature by the input type the compiler
can distinguish them (figure 26). But the us-
age will look a bit ugly since we have to force the this is not possible (figure 29).
cast of any Value Type to get the correct method
as shown in figure 27. The problem here lies in
the fact that the implementation on line 1 in fig- 1 T Create<T>( int v a l u e )
ure 26 matches the method application the most. 2 where T : new( int ) // i n v a l i d
In fact, any time an overloaded method with a 3 {
N ullable<T > should be called the nullable ar- 4 // i n v a l i d
gument has to be either casted to the expected 5 return new T( v a l u e ) ;
Nullable Type (line 10, figure 27) or has to be 6 }
already stored in a variable of the expected Nul-
lable Type (line 13, figure 27). Figure 29: Impossible usage of generic param-
More on the subject of N ullable<T > can be eterized constructor.
found at [19].

4.3.3 new() Optional parameters


new takes it very seriously with the parameter-
The introduction of new has its bene- less constructor. If the new restriction is set on
fits. For example we can write a generic a Type Parameter, it isnt even possible to use a
F actoryM ethod<T >() [32] that looks like figure constructor with an optional parameter. Assum-
28. As simple this may look, thats all new can ing we can create a class Coffee that may contain
offer. But sometimes we may be interested in some sugar. Since some like it without any sugar
calling a constructor with input parameters and we leave this parameter optional.

11
4 THE BAD

Our M achine<T > expects a type that has a 1 i n t e r f a c e IEmptyable<out T> { }


parameterless constructor. But trying to create a 2 c l a s s Cup<T> : IEmptyable<T> { }
M achine<Cof f ee> is not possible even it seems 3
that Coffee has a parameterless constructor (line 4 IEmptyable<IBeverage > cup
12, figure 30). 5 = new Cup<C o f f e e > ( ) ;

1 class Coffee Figure 31: Covariant assignment in C#. Notice


2 { that the covariant usage of T is defined on the
3 C o f f e e ( double s u g a r = 0 . 0 ) interface where it is actually used.
4 { ...
5 }
6 } 1 i n t e r f a c e Emptyable<T> { }
7 2 c l a s s Cup<T>
8 c l a s s Machine<T> 3 implements Emptyable<T> { }
9 where T : new( ) { . . . } 4

10 5 Emptyable<? extends Beverage> cup


11 // v a l i d 6 = new Cup<C o f f e e > ( ) ;
12 var c o f f e e = new C o f f e e ( ) ;
13 Figure 32: Covariant assignment in Java. No-
14 // i n v a l i d tice that the covariant usage of T is defined when
15 var c o f f e e M a c h i n e it is used.
16 = new Machine<C o f f e e > ( ) ;

Figure 30: new() and its limitations. Comparing the examples in figure 31 and 32,
we see the two different approaches, when to us-
ing a Type Parameter in a covariant position.
The solution Java offers looks more flexible since
4.4 Co- & Contravariance we dont have to restrict the Type Parameter
while defining the interface. But this raises the
If we compare the definition and application of following two concerns:
Generics regarding co- & contravariance between
C# and Java we see two major differences. One Too much to think about
difference affects the point in time when we define In Java we have to use the wildcard ? on every
the variance (section 4.4.1) and the other is a re- occasion we want to use the interface in a covari-
striction on what concepts we can define variance ant (or contravariant) manner. This means, we
on (section 4.4.2). have to give a thought on variance every time
we use the interface. And this is probably a lot.
4.4.1 Variance when defined Obviously, equally or more times we defined the
interface.
As seen in section 3.3 in C#, the usage of Type On the other side, we have the restrictive ap-
Parameters is defined on the interface using them proach of C# where the interface forces us to use
(figure 31). In Java we use the opposite approach T only on covariant (or contravariant) positions.
(figure 32). Here we define the usage when the The upside, we have to think on variance once,
concrete type is set. when we define the interface, and when using it,
Assuming we assign a Cup<Cof f ee> to an in- we just have to guarantee the relation between
terface IEmptyable<IBeverage>. According to the types (in our case Cof f ee and IBeverage)
the domain model in appendix A this assignment is correct. And just thinking about their rela-
works since IEmptyable<T > is using T only in tionship is much more easier than dealing with
a covariant position. the covariance itself.

12
Crippling the interface classes [29].
Another concern may be the inflexible usage of Another reason, and slightly more meaningful,
an interface in co- or contravariant way. As men- is its necessity. As long as we can work with
tioned before, in C# we have to decide in what classes in a co- or contravariant way through in-
positions a Type Parameter will be used within terfaces, we are not really restricted. At least
an interface when we define the interface. When we can accomplish the same as if we would have
the Type Parameter is used at an invalid position variance on classes.
(eg. covariant Type Parameter at an contravari- The problem with variance on classes lies in its
ant position) the compiler will raise an error. usability. As soon as we want to use a Type Pa-
Thus we are forced to only use Type Parameters rameter in a field we need it to be invariant since
at positions we allow them when we define the in- a field can be accessed and written to. The only
terface. As a result interfaces will be either com- exception here would be a readonly [21] field
pletely covariant (only used for access) or con- exposing its content in a covariant manner [29].
travariant (only used for modifications). Hence, Thus, the only applicable scenario for a covariant
interfaces contribute their share to get high co- class would be if it represented something im-
hesion [42] which is definitely not bad. mutable ( [25], [24]) or at maximum would only
If we now have a look into Java, where such a allow reductions on its internal values bound to
restriction doesnt exist. The definition of an in- the Type Parameter. On possible class could be
terface doesnt care where the Type Parameters a list only allowing accessing its items, removing
are used. If the interface is used in a covariant or clearing them but not adding any new items.
manner, the compiler will raise an error if we try The benefits of having variance on classes com-
to call a method that uses the Generic Type in a pared to the costs implementing it in the CLR
contravariant manner. As a result, as soon as we are just not reasonable. Despite the costs, we
define to use only the interfaces covariant meth- currently can do everything through interfaces.
ods, we cripple the interface by prohibiting all Hence we couldnt even solve any problems in an
methods that use the Type Parameter at con- easier way. Furthermore, forcing the usage of in-
travariant positions as shown in figure 33. We terfaces doesnt force us to use a specific class
thus we could easily exchange the class imple-
menting the interface.
1 L i s t <? extends Tea> d r i n k s
2 = new A r r a y L i s t <IceTea > ( ) ; We listed these two differences as limitations
3 since it may sound a bit restrictive to develop-
4 // i n v a l i d ers. Even though, we think this restrictive ap-
5 d r i n k s . add (new IceTea ( ) ) ; proaches lead to much cleaner code. And are not
really preventing us from doing less as if these
Figure 33: Covariant List<T > in Java. limitations were not present.

could now argue that in Java it is possible to 5 The Ugly


follow the same design as in C# with separated
interfaces for covariant and contravariant meth- So far, we presented the capabilities of Generics
ods. This is true but we are still not forced to do in C# (section 3) and where we encounter limita-
so by the language. tions (section 4). In this section, we discuss top-
ics where using Generics is possible but running
4.4.2 Variance only on interfaces into a pitfall is likely to happen, or the resulting
solution just wont look that nice.
In C#, we can only use the in and out keywords
on interfaces. Using them in a Generic Class defi-
5.1 Generics and static
nition is not possible. One reason this restriction
exists in C# is based on the fact that the CLR As mentioned in section 3.1, Generic classes will
does only support variance on interfaces but not be expanded and stored separately for every

13
5 THE UGLY

Type Parameters when used for the first time. Superclass which moves some parts of an exist-
Since these expanded Generic classes behave like ing class into a superclass.
different types, static fields can contain unex- If we now apply this Refactoring to our
pected values or references. Cup<T > class, the resulting code will look like
Assuming we want to track the number of cups figure 36. The code seems to look the same. We
we have created. To accomplish that, we extend
the definition of Cup<T > with a counter and a 1 c l a s s BaseCup
property to access its value (figure 34). 2 {
3 s t a t i c int counter ;
1 c l a s s Cup<T> 4 BaseCup ( ) { counter++; }
2 { 5 int Counter
3 s t a t i c int counter ; 6 {
4 Cup ( ) { counter++; } 7 g e t { return counter ; }
5 int Counter 8 }
6 { 9 }
7 g e t { return counter ; } 10
8 } 11 c l a s s Cup<T>
9 } 12 : BaseCup { }

Figure 34: Counting Cup<T > constructor Figure 36: Applying Extract Superclass on
calls. Cup<T >.

If we now create some cups, the counter will just moved the content of Cup<T > into a super-
contain the values shown in figure 35. class and introduced inheritance.
If we now count our cups, we get some inter-
esting results (line 7, figure 37). Even though we
1 var cup1 = new Cup<Cake > ( ) ;
2 // cup1 . Counter == 1
3 var cup2 = new Cup<Cake > ( ) ; 1 var cup1 = new Cup<Cake > ( ) ;
4 // cup2 . Counter == 2 2 // cup1 . Counter == 1
5 var cup3 = new Cup<C o f f e e > ( ) ; 3 var cup2 = new Cup<Cake > ( ) ;
6 4 // cup2 . Counter == 2
7 // cup3.Counter == 1! 5 var cup3 = new Cup<C o f f e e > ( ) ;
6

Figure 35: Counting cups 7 // cup3.Counter == 3!

If we are aware of the CLRs handling of Figure 37: Refactored cup counting
Generic Types, this example sounds completely
reasonable. But if one lacks such background only refactored the code, we altered its behavior,
knowledge, it may result in unexpected behav- too.
ior since static fields seem not to work as they We leave it up to the reader deciding if it is
usually do (line 7, figure 35). still a Refactoring or not. In our opinion it still
Another pitfall in this scenario is waiting for is. The destruction of existing behavior is simply
the developer who is Refactoring. As mentioned based on the implementation of Generics in the
by Fowler [31] Refactoring is the process of CLR and the fact, that we moved something from
changing a software system in such a way that it a Generic into a non-Generic class.
does not alter the external behavior of the code But it gets worse. The same behavior is repro-
yet improves its internal structure. In the same ducible if we inherit from a Generic Type. As-
book, he mentions a Refactoring called Extract suming we have a Cup<T, E> inheriting from

14
5.2 Operator overloading

our Cup<T > (figure 34). The implementation can either overload them as a class member or
of Cup<T, E> looks like 38 where we see that in several cases outside classes [20]. In C#, we
the Type Parameter T is passed to the super- encounter two major differences [14]:
class (line 2). If we now create some instances
1. An operator can only be overloaded within
a class one of the input parameters belongs
1 c l a s s Cup<T, E> to
2 : Cup<T> { }
2. An overloaded operator has to be static
Figure 38: Definition of Cup<T, E>. While the first difference is obvious (despite del-
egate definitions nothing can be defined outside
of Cup<T, E> (figure 39) we notice that the a class) the latter introduces some hard restric-
counter (line 10) in the superclass gets incre- tions.
mented on every constructor call that has the But lets first have a look how we could provide
same Parameter Type (in our case Coffee). a simple implementation to sum some amount
of Tea. Figure 40 shows how an + operator
1 var cup1 = could be implemented for Tea and in figure 41
2 new Cup<C o f f e e , Milk > ( ) ; we mention its usage.
3 var cup2 =
4 new Cup<C o f f e e , WhippedCream > ( ) ; 1 c l a s s Tea
5 var cup3 = 2 {
6 new Cup<Cake , WhippedCream > ( ) ; 3 double amount ;
7 4 Tea ( double a ) { amount = a ; }
8 var cup4 = new Cup<C o f f e e > ( ) ; 5 s t a t i c Tea operator+
9 6 ( Tea l h s , Tea r h s )
10 // cup4.Counter == 3! 7 {
8 return new Tea
Figure 39: Counting Cup<T, E> 9 ( l h s . amount + r h s . amount ) ;
10 }
To sum up, using static in Generic Types is 11 }
dangerous. Especially, when we introduce inher-
itance. And we have to be aware of potential side Figure 40: Tea with + operator .
effects when Refactoring Generic Types contain-
ing static values (e.g. fields).
1 var f i r s t D r i n k = new Tea ( 5 ) ;
5.2 Operator overloading 2 var secon dDrink = new Tea ( 3 ) ;
3
In section 2, we mentioned the difference between
implicitly and explicitly Generic Programming. 4 var iDrank = f i r s t D r i n k
Since C# forces us to define explicitly the capabil- 5 + sec ondDrink ;
ities of Type Parameters with Type Constraints,
we are bound to the possibilities the language Figure 41: Drinking Tea
offers. These Type Constraints cover a broad
area of restrictions we can force the Type Pa- Now we know how operators can be overloaded
rameters have to fit in. But there is one concept for types. Lets have a look how this may work
we cant enforce by the given Type Constraints. in context of Generic Types.
And these are operators. First of all, the forced usage of the
As in C++, it is possible to overload operators static keyword when overloading operators
in C#. But we are slightly limited in the pos- reduces our options to introduce opera-
sibilities where to overload them. In C++, we tor overloads on Type Parameters. Since

15
5 THE UGLY

interfaces dont allow any static declarations 1 c l a s s Pot<T> : I F i l l a b l e <T>


and the other Type Constraints wont help ei- 2 where T : BevOps<T>, I B e v e r a g e
ther, only a superclass Type Bound can be used 3 {
(section 5.2.1). Or we could simply abandon the 4 T liquid ;
Type System (section 5.2.2). 5 void F i l l W i t h (T l i q u i d )
6 {
5.2.1 Operator inheritance 7 liquid = liquid + liquid ;
8 }
Lets have a look on the approach with a su-
9 }
perclass. The provided solution is a slightly ex-
tended version found at [18].
In C#, operator overloads can be inherited. Figure 43: P ot<T >
This means when we overload operators any sub-
class is aware of, and could work with them.
If we want to accumulate the amount of liquid use operators within Generic Types, we want to
in a P ot<T >, we have to introduce a superclass discuss some ugly aspects.
providing the + operator. Therefore we create First of all, we need a strong dependency
a class BevOps<T > (figure 42) we can inherit (inheritance) between our Type Parameter and
from. In figure 43, we show a P ot<T > we can BevOps<T > to setup the Type Constraint (line
2, figure 43). But introducing a new superclass
1 abstract c l a s s BevOps<T> into an existing type hierarchy is not always pos-
2 { sible.
3 protected abstract T Add(T r h s ) ; Second, since BevOps<T > is invariant (sec-
4 tion 4.4.2), as soon as we inherit from Tea, we
5 s t a t i c T operator+ cant use P ot<T > anymore for the new subclass
6 ( BevOps<T> l h s , T r h s ) (figure 44). P ot<IceT ea> expects IceTea to be
7 { a subclass of BevOps<IceT ea> but it is one of
8 return l h s == null BevOps<T ea> since we inherited from Tea.
9 ? rhs
10 : l h s . Add( r h s ) ;
11 } 1 c l a s s IceTea : Tea { . . . }
12 } 2
13 3 // v a l i d
14 c l a s s Tea : BevOps<Tea> 4 var t ea Po t = new Pot<Tea > ( ) ;
15 { ... 5
16 override Tea Add ( Tea r h s ) 6 // i n v a l i d
17 { 7 var i ce T e aP o t = new Pot<IceTea > ( ) ;
18 return new Tea (
19 amount + r h s . amount ) ;
Figure 44: Subclass of Tea is not usable as Type
20 }
Parameter on P ot<T >.
21 }

Figure 42: Defining and unsing BevOps<T >. If we want to get rid of this strong depen-
dency, we could remove the Type Parameter of
accumulate the filled-in liquid using the + op- BevOps<T >. But in this case, we would have
erator. This is possible since the BevOps<T > to make some assumptions on the type returned
superclass is used as a Type Constraint on Type by the + operator. In particular we could only
Parameter T . work with BevOps types and would have to cast
Even though we provided a solution how to them into the correct types.

16
5.2 Operator overloading

5.2.2 Using dynamic really a key benefit of C#s rich Generics imple-
mentation. But compared to the first possible
With the previously presented solution on work-
implementation, discussed in section 5.2.1, using
ing with operators by using a superclass, we tried
dynamic seems to be the simpler implementa-
to stick with the Type System. Another solu-
tion as long as we know what we are doing.
tion to enable the usage of operators on Type
Furthermore, using dynamic slightly reminds
Parameters would be by abandoning the Type
us of using a template in C++ (figure 47).
System. In C#, we can do this with the dy-
As mentioned at the beginning, templates in
namic keyword [12].
C++ implicitly force its Type Parameters to sup-
If dynamic is used, we basically bypass the
port whatever the templates implementation
Type System and expect everything will work at
needs. The main difference, between using dy-
runtime, or we will then get an exception. The
idea for this solution is based on the summary
found at [18] and the discussion on [6]. 1 c l a s s Tea
The implementation in this case would look 2 {
like figure 45. We assume T implements the + 3 private :
4 double amount ;
1 c l a s s Pot<T> : I F i l l a b l e <T> 5 public :
2 where T : I B e v e r a g e 6 Tea ( double amount )
3 { 7 : amount ( amount ) { . . . }
4 T liquid ; 8 Tea operator+(const Tea &r h s )
5 void F i l l W i t h (T l i q u i d ) 9 const {
6 { 10 return Tea ( amount
7 liquid = 11 + r h s . amount ) ;
8 ( dynamic ) l i q u i d + l i q u i d ; 12 }
9 } 13 }
14
10 }
15 template<typename T>
16 c l a s s Pot : public F i l l a b l e <T>
Figure 45: Using the + operator with dy- 17 {
namic. 18 private :
19 double liquid ;
operator that takes something of type T on both 20 public :
sides of the operator and will return a result of 21 void F i l l W i t h (T c o n t e n t )
type T . 22 override {
If we now use our first implementation of Tea 23 liquid = liquid + liquid ;
(figure 40), we can use Tea as a valid Type Pa- 24 }
rameter for P ot<T > (figures 45 & 46). As men- 25 }

1 var t ea Po t = new Pot<Tea > ( ) ; Figure 47: P ot<T ea> example in C++.
2 t ea Po t . F i l l W i t h (new Tea ( 4 ) ) ;
3 t ea Po t . F i l l W i t h (new Tea ( 5 ) ) ; namic in C# (in the discussed situation) and the
C++ template, is the point in time you may notice
Figure 46: Filling a P ot<T ea>. the + operator is missing.
As mentioned before, in C# the check, if the +
tioned, we bypass the Type System but dont operator exists, is done at runtime. If missing,
have to introduce any new classes or dependen- we will get a Microsoft.CSharp.RuntimeBinder.
cies. RuntimeBinderException with a message like
The downside of using dynamic is the loss of Cannot implicitly convert type string to Tea
compile-time Type Checking. This is otherwise but no hint that there is an operator missing.

17
6 CONCLUSION

Having a deeper look at the message we real- about a vending machine that could provide the
ize that not even the missing + operator is the cup with the beverage?
root cause of the exception but the assignment A possible V endingM achine<C, T > could
of the value back to the field. The missing + look like figure 49 and its application like figure
operator simply results in a string contain- 50.
ing the type of the field or parameter the dy-
namic keyword was used on. 1 c l a s s VendingMachine<C, T>
But there exists the possibility to get an ex- 2 where C : I F i l l a b l e <T>, new( )
ception message we can really work with. We 3 where T : IBeverage , new( )
can slightly modify our usage of the + opera- 4 {
tor by moving dynamic to the right-hand side 5 C GetProduct ( ) { . . . }
parameter (of an expected binary operator) as 6 }
shown in figure 48. Now we will get an exception
message like Operator + cannot be applied to Figure 49: V endingM achine<C, T >
operands of type Tea and Tea which really
tells us what the problem is. Hence, we have

1 var machine
1 liquid = 2 = new VendingMachine
2 l i q u i d + ( dynamic ) l i q u i d ; 3 <Cup<C o f f e e >, C o f f e e > ( ) ;
4
Figure 48: dynamic on right-hand side param- 5 var cup = machine . GetProduct ( ) ;
eter of + operator.
Figure 50: Application of
to keep in mind when using dynamic to enable V endingM achine<C, T >.
operator overloading we should cast the right-
hand side parameter. We could go even further We see right away that we have to specify
with using dynamic but this would be out of the Coffee twice. We could get rid of this du-
scope of this paper. If anyone is interested, there plication, if we simply replace Cup<Cof f ee>
is a whole section on dynamic in [26]. with Cup<IBeverage>. But this would only
On the other side, C++ will say that the + op- be cosmetic. Omitting the Type Parameter of
erator is missing at compile-time, right away. Cup<T > is also not an option since the con-
straint (line 2, figure 49) wouldnt be fulfilled,
The goal of this section was to present a solution anymore.
on how to use operators within Generic Types. What we actually want, is the ability to expect
We know there is the possible solution of provid- a Type Parameter to be a Generic Type. Thus we
ing an interface with methods instead of using could shorten the Type Parameters by stacking
operators. In this case we could have simply used them. The definition and application then would
the interface as Type Constraint. look like figure 51. But unfortunately that is not
But using operators is possible with Generics. supported.
Its just a question of how much we want to pay
for that.
6 Conclusion
5.3 Stacking Type Parameters We gave a broad summary on the capabilities of
Generics in C#. While the capabilities in the first
We introduced in section 3.2.2 a M achine<T > part are well known among C# developers, since
for beverages. The downside of this they are normally mentioned when learning the
M achine<T > was that we had to provide language, not many are familiar with their limi-
our own Cup<T > to fill the beverage in. How tations. This is mainly due the fact that Generics

18
1 c l a s s VendingMachine<C<T>> ics where a simple interface may also had done
2 where . . . the trick. The line between using Generics and
3
over-engineering is thin and blur.
4 var machine But at the end, we cant imagine an object-
5 = new VendingMachine oriented world with type-safety but without
6 <Cup<C o f f e e > >(); Generics or some other implementation of Pa-
rameterized Types. They make coding easier, es-
pecially when working with containers (List, Set,
Figure 51: Definition and application of etc), as long as we know what we are doing.
V endingM achine<C<T >>.

in C# are a mighty concept. If limits are reached,


one is trying to find them (as we did writing this
paper), or Generics are not that well known by
the developer.
Despite Generics in Java, Generics in C# are
deeply integrated into the language, even into the
virtual machine. Furthermore, rich support con-
straining Type Parameter is offered. Used in the
correct way, Generics can be a real support pur-
suing the DRY-principle.
But Generics are no silver bullet. If Gener-
ics are used, there is some serious software engi-
neering effort expected from the developer. One
should know when and where to use Generics es-
pecially what it means for the types that will be
used as Type Parameters, or limitations will be
hit, soon.
Furthermore, Generics made the developer
deal with co- & contravariance when defining
Generic Types. In the era before Type Parame-
ters the types within a signature simply had to be
the same, when calling we could put in subtypes
and expect supertypes as a result. Generics re-
moved the forced signature equality for the price
that a developer now has to deal with it. Luckily
the designer of C# (an the CLR) did a great job
and dealing with co- & contravariance is not that
hard if one has understood the concept.
After one has mastered Generics, pitfalls might
not open often. But knowing, applying and
preaching Generics, one has always be aware of
their power. Even though the smell Speculative
Generality was mentioned in Fowlers book on
Refactoring [31] in 1999, when Generics in Java
only existed on drafts and forks of the language
and C# not even existed, Generics brought Spec-
ulative Generality to a whole new level. Many
(including the authors) have introduced Gener-

19
REFERENCES

References
[1] Ecma tr/89: Common language infrastructure (cli) - common generics. http://www.
ecma-international.org/publications/techreports/E-TR-089.htm, 6 2006.

[2] Ecma-335: Common language infrastructure (cli). http://www.ecma-international.org/


publications/standards/Ecma-335.htm, 6 2012.

[3] C++ . http://www.cplusplus.com/, 11 2014.

[4] C# language specification 5.0. http://www.microsoft.com/en-us/download/details.aspx?id=


7029, 11 2014.

[5] Java 8 language specification. https://docs.oracle.com/javase/specs/jls/se8/html/index.


html, 11 2014.

[6] Arithmetic operator overloading for a generic class in C# . http://stackoverflow.com/


questions/756954/arithmetic-operator-overloading-for-a-generic-class-in-c-sharp, 5
2015.

[7] as. https://msdn.microsoft.com/en-us/library/cscsdfbt.aspx, 5 2015.

[8] Boxing and unboxing. https://msdn.microsoft.com/en-us/library/yz2be5wk.aspx, 5 2015.

[9] Common language runtime (clr). https://msdn.microsoft.com/en-us/library/8bs2ecf4%28v=


vs.110%29.aspx, 5 2015.

[10] Constraints on type parameters. https://msdn.microsoft.com/en-us/library/d5x73970.aspx,


5 2015.

[11] Create generic method constraining t to an enum. http://stackoverflow.com/questions/79126/


create-generic-method-constraining-t-to-an-enum, 6 2015.

[12] dynamic. https://msdn.microsoft.com/en-us/library/dd264741.aspx, 5 2015.

[13] Enum type constraints in C# . http://stackoverflow.com/questions/1331739/


enum-type-constraints-in-c-sharp, 6 2015.

[14] General rules for operator overloading. https://msdn.microsoft.com/en-us/library/4x88tzx0.


aspx, 5 2015.

[15] Generic properties. http://www.boyet.com/Articles/GenericProperties.html, 5 2015.

[16] N ullable<T > structure. https://msdn.microsoft.com/en-us/library/b3h38hb0%28v=vs.110%


29.aspx, 5 2015.

[17] Generics for .net. http://research.microsoft.com/en-us/um/people/akenn/generics/index.


html, 5 2015.

[18] Making generics add up. http://www.developerfusion.com/article/84413/


making-generics-add-up/, 5 2015.

[19] Nullable types (C# programming guide). https://msdn.microsoft.com/en-us/library/


1t3y8s4s.aspx, 5 2015.

[20] operator overloading. http://en.cppreference.com/w/cpp/language/operators, 5 2015.

20
REFERENCES

[21] readonly. https://msdn.microsoft.com/en-us/library/acdd6hb7.aspx, 6 2015.

[22] Reference types. https://msdn.microsoft.com/en-us/library/490f96s2.aspx, 5 2015.

[23] Value types. https://msdn.microsoft.com/en-us/library/s1ax56ch.aspx, 5 2015.

[24] Why does C# (4.0) not allow co- and contravariance in generic
class types? http://stackoverflow.com/questions/2541467/
why-does-c-sharp-4-0-not-allow-co-and-contravariance-in-generic-class-types, 5
2015.

[25] Why isnt there generic variance for classes in C# 4.0? http://stackoverflow.com/
questions/2733346/why-isnt-there-generic-variance-for-classes-in-c-sharp-4-0/
2734070#2734070, 5 2015.

[26] Joseph Albahari and Ben Albahari. C# 5.0 in a Nutshell: The Definitive Reference. OReilly
Media, Inc., 2012.

[27] James C Dehnert and Alexander Stepanov. Fundamentals of generic programming. In Generic
Programming, pages 111. Springer, 2000.

[28] Gabriel Dos Reis and Bjarne Stroustrup. Specifying C++ concepts. ACM SIGPLAN Notices,
41(1):295308, 2006.

[29] Burak Emir, Andrew Kennedy, Claudio Russo, and Dachuan Yu. Variance and generalized con-
straints for C# generics. In ECOOP 2006Object-Oriented Programming, pages 279303. Springer,
2006.

[30] Martin Fowler. Code smell. http://martinfowler.com/bliki/CodeSmell.html, 2 2006.

[31] Martin Fowler, Kent Beck, J Brant, William Opdyke, and Don Roberts. Refactoring: Improving
the design of existing programs. Addison-Wesley Reading, 1999.

[32] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design patterns: elements of
reusable object-oriented software. Pearson Education, 1994.

[33] Ronald Garcia, Jaakko Jarvi, Andrew Lumsdaine, Jeremy G Siek, and Jeremiah Willcock. A
comparative study of language support for generic programming. In OOPSLA, volume 3, pages
115134, 2003.

[34] Andre Gasser. The concept of subtyping explained from a type systems perspective. In Seminar:
Types and Program Transformation, 2015.

[35] Daniel Giovannelli. Programming in algorithms: Generic programming and its implementation.
2013.

[36] Andy Hunt and Dave Thomas. The Pragmatic Programmer: From Journeyman to Master. Addison-
Wesley Professional, 1999.

[37] Andrew Kennedy. Variance and generalized constraints for C# generics. http://research.
microsoft.com/en-us/um/people/akenn/generics/ECOOP06.ppt, 7 2006.

[38] Andrew Kennedy and Claudio V Russo. Generalized algebraic data types and object-oriented
programming. In ACM SIGPLAN Notices, volume 40, pages 2140. ACM, 2005.

21
REFERENCES

[39] Andrew Kennedy and Don Syme. Design and implementation of generics for the. net common
language runtime. In ACM SigPlan Notices, volume 36, pages 112. ACM, 2001.

[40] Andrew Kennedy and Don Syme. Combining generics, pre-compilation and sharing between
software-based processes. contexts, 3(19):11, 2004.
[41] Andrew Kennedy and Don Syme. Pre-compilation for .net generics. Microsoft Research, Cambridge,
UK, 2005.
[42] Craig Larman. Applying UML and patterns: an introduction to object-oriented analysis and design
and iterative development. Pearson Education India, 2005.
[43] David R Musser and Alexander A Stepanov. Generic programming. In Symbolic and Algebraic
Computation, pages 1325. Springer, 1989.
[44] Jaime Nino. The cost of erasure in java generics type system. Journal of Computing Sciences in
Colleges, 22(5):211, 2007.
[45] Benjamin C Pierce. Types and programming languages. MIT press, 2002.
[46] Jon Skeet. C# in Depth. Manning, 2008.
[47] Andrew Sutton and Bjarne Stroustrup. Design of concept libraries for c++. In Software Language
Engineering, pages 97118. Springer, 2012.
[48] Dachuan Yu, Andrew Kennedy, and Don Syme. Formalization of generics for the. net common
language runtime. In ACM SIGPLAN Notices, volume 39, pages 3951. ACM, 2004.

22
A Domain model

Figure 52: Domain model of used examples.

The domain model in figure 52 uses a C#-ish notation of Parameterized Types and Type Parameter
Constraints (annotations see table 1) but should be easily understood.

in T Denotes a Type Parameter T will only be used in a contravariant position (input parameter)
out T Denotes a Type Parameter T will only be used in a covariant position (return type)
where T Denotes a Type Constraint on possibly usable types for the Type Parameter T

Table 1: Annotations on domain model.

23

Potrebbero piacerti anche