I've read an excellent article on MSDN regarding Generics in C#.
The question that popped in my head was - why should i be using generic constraints?
For example, if I use code like this:
public class MyClass<T> where T : ISomething
{
}
can't I switch ALL references of T
in this class with ISomething
?
What's the benefit of using this approach?
The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.
Interface Type Constraint You can constrain the generic type by interface, thereby allowing only classes that implement that interface or classes that inherit from classes that implement the interface as the type parameter.
Declaring those constraints means you can use the operations and method calls of the constraining type. If your generic class or method uses any operation on the generic members beyond simple assignment or calling any methods not supported by System. Object, you'll apply constraints to the type parameter.
Generics shift the burden of type safety from you to the compiler. There is no need to write code to test for the correct data type because it is enforced at compile time. The need for type casting and the possibility of run-time errors are reduced. Better performance.
So, you can pass reference types such as class, interface, delegate, or array type. Passing value types will give a compile-time error, so we cannot pass primitive data types or struct types. The following table lists the types of generic constraints. The type argument must be any class, interface, delegate, or array type.
Constraints can specify interfaces, base classes, or require a generic type to be a reference, value or unmanaged type. They declare capabilities that the type argument must possess. For example, you can declare a generic class, MyGenericClass, such that the type parameter T implements the IComparable<T> interface:
Constraints on type parameters (C# Programming Guide) Constraints inform the compiler about the capabilities a type argument must have. Without any constraints, the type argument could be any type. The compiler can only assume the members of Object, which is the ultimate base class for any .NET type.
The constraint enables the generic class to use the Employee.Name property. The constraint specifies that all items of type T are guaranteed to be either an Employee object or an object that inherits from Employee. Multiple constraints can be applied to the same type parameter, and the constraints themselves can be generic types, as follows:
You ask, "can't I switch ALL references of T
in this class with ISomething
?" So I think you mean to compare:
public class MyClass<T> where T : ISomething
{
public T MyProperty { get; set; }
}
With:
public class MyClass
{
public ISomething MyProperty { get; set; }
}
In the second example, MyProperty
is only guaranteed to be an instance of ISomething
. In the first example, MyProperty
is whatever T
is, even if that is a specific subtype of ISomething
. Consider a concrete implementation of ISomething
:
public class MySomething : ISomething
{
public string MyOtherProperty { get; set; }
}
Now, if we use the first, generic, example, we could have:
MyClass<MySomething> myClass = new MyClass<MySomething>();
Console.WriteLine(myClass.MyProperty.MyOtherProperty);
On the other hand, if we used the second example, we wouldn't be able to access MyOtherProperty
since it's only known to be an ISomething
:
MyClass myClass = new MyClass();
Console.WriteLine(myClass.MyProperty.MyOtherProperty); // Won't compile, no property "MyOtherProperty"
On a different note, the reason these type constraints are useful is that you can refer to MyProperty
(type T
) and access members of ISomething
. In other words, if ISomething
were declared like:
public interface ISomething
{
public string SomeProperty { get; set; }
}
Then you could access MyProperty.SomeProperty
. If you omitted the where T : ISomething
then you wouldn't be able to access SomeProperty
since T
would only be known to be of type object
.
Type Safety. For example, suppose you're creating a container. You can pass in something to that container and retrieve it in the proper form without having to do any casts later by parameterizing the container. You're simply defining constraints on the types of things that you're willing to store in your container.
Here's an example of the difference, by just using List<>
Image list wouldn't be generic but it would just use IListElement
everywhere it used the generic instead. Now Imagine you have an object that's something like this.
class Element : IListElement
{
public string Something { get; set; }
}
now I could just do list.Add(element);
and there wouldn't be a difference with a real List<Element>
. However when I retreive data it's a different story, if I use the list that uses IListElement
then I have to cast my data back so I can get the Something
out of it. Thus i'd have to do:
string s = ((Element)list[0]).Something;
while with the generic I can just do:
string s = list[0].Something;
saves a lot of trouble, ofcourse it goes a bit further than that but I think you can get the idea from this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With