28

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?


7 답변


47

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.


8

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.


4

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.


2

Well for a start, you can call methods defined in ISomething within the code for the generic method / methods on the generic class. If T was allowed to be any type then this would not be possible (although you could always do some runtime casting).

So it allows you to enforce compile-time constraints on what T can be and therefore rely on these constraints when you write the code - turning runtime errors into compile time errors.


1

Yes you can use ISomething in place of T , but that will manually close the generic type to an ordinary class. It wont be a generic type any more. By using T, you keep the type open to as many ISomething subtypes as you want. Code reuse without compromising type safety is the key benefit here. For example if you use a Stack of ISomethings, you can push any ISomething onto the stack but a pop has to occur with a downcast to the actual subtype of ISomething for it to be useful. Downcasting creates a potential failure point, which will not be there in a generic Stack<T> where T:ISomething


0

Consumer of your class gets the benefit of increased type-safety, among others.

class Widget : IPokable { }

// No generics
Widget w = (Widget)list[0]; // cast can fail

// With generics
Widget w = list[0];

Without generics, if list was containing IPokable objects, cast is still necessary.

Class you're implementing gets the benefit of using specific methods on the generic object.

class PokableList<T> where T : IPokable {
    public T PokeAndGet() {
        currentObj.Poke();
        return currentObj;
    }
}


0

This youtube video actually demonstrates the importance of generic constraints https://www.youtube.com/watch?v=GlqBRIgMgho.

Now below goes a long textual answer.

“Generic’s helps to decouple logic from the data type. So that we attach any data type with any logic for high reusability.”

But many times some logic’s can be attached to only specific data types.

public class CompareNumeric<UNNKOWDATATYPE>
{
        public bool Compareme(UNNKOWDATATYPE v1, UNNKOWDATATYPE v2)
        {
            if (v1 > v2)
            {return true;}
            else
           {return false;}
        }
}

For example above is a simple generic class which does comparison if one number is greater than other number. Now the greater and less than comparison is very specific to numeric data types. These kind of comparison’s cannot be done on non-numeric types like string.

So if some uses the classes with “int” type its perfectly valid.

CompareNumeric<int> obj = new CompareNumeric<int>();
bool boolgreater = obj.Compare(10,20);

If someone uses it with “double” data type again perfectly valid.

CompareNumeric<double> obj = new CompareNumeric<double>();
bool boolgreater = obj.Compare(100.23,20.45);

But using string data type with this logic will lead undesirable results. So we would like to restrict or put a constraint on what kind of types can be attached to a generic class this is achieved by using “generic constraints”.

CompareNumeric<string> obj = new CompareNumeric<string>();
bool boolgreater = obj.Compare(“interview”,”interviewer”);

Generic type can be restricted by specifying data type using the “WHERE” keyword after the generic class as shown in the below code. Now if any client tries to attach “string” data type with the below class it will not allow, thus avoiding undesirable results.

public class CompareNumeric<UNNKOWDATATYPE> where UNNKOWDATATYPE : int, double
{

}


Linked


Related

Latest