Why Use Generic Constraints in C#

Why use generic constraints in C#

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.

Generic constraints to a specific class, why?

Perhaps this simple example might help.

If I have these classes:

public class ListOfCars<T> : List<T> where T : Car { }

public abstract class Car { }
public class Porsche : Car { }
public class Bmw : Car { }

...and then if I write this code:

var porsches = new ListOfCars<Porsche>();

// OK
porsches.Add(new Porsche());

//Error - Can't add BMW's to Porsche List
porsches.Add(new Bmw());

You can see that I can't add a BMW to a Porsche list, but if I just programmed off of the base class it would be allowed.

Why to use generic with interface constraint?

The two interfaces are very different, because List<T> and List<IBase> are very different. Let's suppose there is a class A that implements IBase. This, for one, does not compile:

IGeneric noTypeParameter = ...
noTypeParameter.property = new List<A>();

But this does:

IGeneric<A> hasTypeParameter = ...
noTypeParameter.property = new List<A>();

This is because List is not covariant on T. In fact it is invariant. See also: Convert List<DerivedClass> to List<BaseClass>

If you use IEnumerable<T> and IEnumerable<IBase> , then both of the above code snippets will compile, but the two IGeneric interfaces are still different, because:

IGeneric noTypeParameter = ...
IEnumerable<A> enumerable = noTypeParameter.property; // does not compile

IGeneric<A> hasTypeParameter = ...
IEnumerable<A> enumerable = noTypeParameter.property; //does compile

So basically, with the generic parameter, you are able to pass specific types of List to the interface, and get specific types of List out. However, you can't "store a property of any IBase implementation". Without a type parameter, you are able to store a property of any IBase implementation, but you can't get specific types out of/into IGeneric.

generic NOT constraint where T : !IEnumerable

No - there's no such concept either in C# or in the CLR.

Generic constraints with C#

where TType : ParameterValue<TType>

This is a recursive generic constraint, which will simplify to TType : ParameterValue<XYZParameterValue> where XYZParameterValue : ParameterValue<TType> which is not what you want, because in your case the actual type (e.g. string) does not inherit its corresponding ParameterValue (ParameterValue<string>).

Your generic constraints would work when using a generic interface/base class which is implemented/inherited by the same type which it is generic over, like the IComparable<T> interface which is implemented by the type T (i.e. System.String : IComparable<System.String>).

Instead, I'd do the following:

public class Parameter<T> : Parameter
{
public Parameter(string name, ParameterValue<T> value) : base(name)
{
Value = value;
}

public ParameterValue<T> Value { get; set; }
}

You'd have to change ParameterManager methods to a similar form too:

public void AddParameter<T>(string name, ParameterValue<T> value)
{
_parameters[name] = new Parameter<TType>(name, value);
}

public ParameterValue<T> FindParameterValue<T>(string name)
{
var parameter = _parameters[name];
var parameterTyped = parameter as Parameter<TType>;
return parameterTyped?.Value;
}

Side Note: Naming the type constraint TType doesn't make any sense by general conventions since the T prefix in a type parameter means "type" and so T would be enough.

Is there a generic constraint I could use for the + operator?

There are no such devices in C#. A few options are available, though:

  • in C# 4.0 and .NET 4.0 (or above), use dynamic, which supports + but offers no compile time checking
  • in .NET 3.5 (or above), MiscUtil offers an Operator class which makes operators available as methods - again, without any compile-time checking

So either:

return (dynamic)left.Evaluate(context) + (dynamic)right.Evaluate(context);

or

return Operator.Add(left.Evaluate(context), right.Evaluate(context));

Generic constrain for Lists and Collections

Why don't:

public int GetCount<T>(ICollection<T> collection)
{
return collection.Count;
}

C# use constraint generics to allow function only on parent types

Most answers give a partial answer so I'll compile all the answers and reasons why they may not work for specific cases here.

Option 1 : reversing caller & callee (m.rogalski's comment)

public class SomeClass<Parent> //instead of derived
where Parent : class
{
public void call<Derived>()
where Derived : class, Parent
{
ParentChecker<Derived, Parent> checker = new ParentChecker<Derived, Parent>();
}
}

This is the closest to a complete compile time check as you can get of course but it might not be feasible depending on the system design (which was my case).

Option 2 : using intermediate template type (Zoran Horvat's Answer)

public class SomeClass<Derived>
where Derived : class
{
public void call<Parent, NewDerived>()
where Parent : class
where NewDerived: Derived, Parent
{
ParentChecker<NewDerived, Parent> checker =
new ParentChecker<NewDerived, Parent>();
}
}

This works so long as you use it with correct input (which will be most cases) but the reason to use compile time checking is mostly to avoid having miss use of something so I don't feel it's a complete answer. The usability is weaker than ideal (you need to specify both types at the moment of the call) and it has potential counter examples such as :

public interface GrandP_A {}
public interface GrandP_B {}
public class Parent : GrandP_A {}
public class Child : Parent, GrandP_B {}

SomeClass<Parent> instance = new SomeClass<Parent>();
instance.call<GrandP_B, Child>();//this compiles

The goal of SomeClass is to check, that SomeClass's generic type parameter is derived from the first generic type of the call function. In this case GrandP_B. However, GrandP_B is not a parent class to Parent but the call still compiles. So again, this only works when you use it well (passing Parent as the second generic type of .call)

Option 3 : Runtime checking

I had to compromise and use Runtime Checking instead. This is obviously not the way to go as a compile time solution but it's the only one that allowed the specific design I had in mind. I'll still mention it here as a partial answer in case it helps someone in the future. For more info, check this other answer on that specific question

public class SomeClass<Derived> 
where Derived : class
{
public void call<Parent>()
where Parent : class
{
if(typeof(Derived).IsSubclassOf(typeof(Parent)))
{
//do your stuff
}
else
{
throw new Exception("Must be called wih parent classes only!");
}
}
}

Generic type in generic constraint

You can't use unbound generic types in type constraints. You'd have to add a third type parameter, like this:

public abstract class ColumnFilter<TCell, TFilterControl, TFilterControlType> : ColumnFilter
where TFilterControl : FilterControl<TFilterControlType>, new()
where TCell : IView, new()
{
}

Or create a non-generic base type of FilterControl:

public FilterControl { }
public FilterControl<T> : FilterControl { }

public abstract class ColumnFilter<TCell, TFilterControl> : ColumnFilter
where TFilterControl : FilterControl, new()
where TCell : IView, new()
{
}

You can also make the base type abstract with internal constructors if you want to force consumers to use your generic derived type.



Related Topics



Leave a reply



Submit