Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
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.
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
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 2: Java: Cup<T > Figure 4: Resulting Java Bytecode of Cup<T >.
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
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).
7
3 THE GOOD
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
9
4 THE BAD
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
11
4 THE BAD
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.
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
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
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 >>.
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.
20
REFERENCES
[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.
[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
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
23