Where method restriction - c#

I've see generic methods with this where keyword like
public static void DoStuff<T>() where T :
where seems to restrict the type of T in a specific way.
Question: how does this where work? Is it only possible to ristrict to interfaces like where T : IComparable<T> or are there different ways aswell?

It's called a generic type parameter constraint
You can constraint T to inherit from a base class, implement an interface, be a reference type, be a value type, have a parameterless constructor...
The compiler checks that the type parameter meets these requirements.
In your case, the constraint allows to call IComparable<T> methods in the DoStuff method.

Related

how to constraint the Type parameter to a certain interface only

Would it be possible to constraint a Type parameter to only allow caller to pass in class type that implements a certain interface? Take below as example:
public void RegisterProcessors(params Type[] types)
As it is now, caller can pass in any class type to this method. For example:
RegisterProcessors(typeof(string));
But what I really want is to only allow caller to pass in type that implements the IProcessor interface. Is that possible? Something like below, of course, below is syntactically wrong
public void RegisterProcessors(params Type<IProcessor>[] types)
No, basically. If you were using generics, you could use
public void RegisterProcessor<T>() where T : IProcessor
But you can't combine that with params, so the caller would need to invoke it once per type. Which might be fine.

Generic interface with generic elements and constraints

I would like to define a generic library containing generic elements :
public interface ILibrary<T>
My generic elements are defined like this :
interface ILibElement<T, IVersion<T>>
I would like to add the constraint saying that elements in the Library must implement ILibElement. So I thought I could change my definition of the ILibrary like this :
public interface ILibrary<T> where T : ILibElement<T, IVersion<T>>
But I must add type parameters.
So I thought doing something like :
public interface ILibrary<ILibElement<T, IVersion<T>>>
But T is undefined.
I could not find the right syntax. How could I express such a constraint ?
I believe you're just looking to do this:
where T : ILibElement<T, IVersion<T>>
since ILibElement is expressed as ILibElement<T, IVersion<T>> you just need to specifically implement that interface with the T that you're implementing.
I think you can try a logic like this
public interface ILibrary<T,V> where T:ILiBelement<T,V> where T:ILiBelement<T,V>
where V:IVersion<V>
{
}
interface ILiBelement<T,V> where V:IVersion<T>
{
}
interface IVersion<T>
{
}
hope this help
Generic type parameters must be specified as identifiers; the constraints may use more complex nested formulations, but the parameters themselves must not. If one wanted a generic method which took a parameter of some type Nullable<T>, one wouldn't specify it as:
void ThisWontWork<Nullable<T>>(T it) where T:struct {...}
but instead as:
void ThisWillWork<T>(Nullable<T> it) where T:struct {...}
If your collection may be holding further-qualified generic things which will all be of one type, you may wish to add that type as a type parameter for your collection:
class ListOfLists<T,U> where T:IList<U>
If your collection will be holding things that implement a generic interface with a variety of generic types, but you'll only need to use members which do not involve such types, then you should if possible have the members which don't care about the generic type segregated into their own interface, which could be inherited by the full generic. For example, although Microsoft didn't design their IDictionary this way, it could have been defined as something like (in part:)
interface ICheckIfContained
{ bool Contains(object it); }
interface ICheckIfContained<in T> : ICheckIfContained
{ bool Contains(T it); }
interface IDictionary<in TKey, out TValue> : ICheckIfContained<TKey> ...
If one wants to hold a collection of references to dictionaries purely for the purpose of scanning the keys keys in them, without regard for what those keys would map to, a generic parameter constrained to ICheckIfContained could serve such a purpose.

C# Interface Contraints

I am experimenting with Generic Constraints. When declaring a constraint on a class as so:
public class DocumentPrinter<T> where T : IFooDoc
I am able to access method declared by IFooDoc within the methods of the DocumentPrinter class. However, if I make DocumentPrinter implement an interface that declares the contraint, eg:
public interface IDocumentPrinter<T> where T : IFooDoc
{
void Add(T fooDoc);
void PrintFoos();
}
and then declare DocumentPrinter as so:
public class DocumentPrinter<T>: IDocumentPrinter<T>
properties/methods of IFooDoc instances are no longer available within the methods of the Document printer. It seems that I must explicitly declare an interface constraint upon the class itself if I am to access members declared by the type.
Do I understand this correctly or is it possible to declare the constraint on the interface and to have that constraint realized by the class?
Do I understand this correctly or is it possible to declare the constraint on the interface and to have that constraint realized by the class?
Correct. You have1 to declare the constraint on the type parameters for the generic class as well. Just because you named the type parameter in DocumentPrinter<T> to have the same name as the type parameter in IDocumentPrinter<T> does not mean that they are the same types. When you declare
public class DocumentPrinter<T> : IDocumentPrinter<T>
you are in fact saying to use T that parameterizes DocumentPrinter<T> to parameterize IDocumentPrinter<T> and now they are the same types. But for that to be legal, T from DocumentPrinter<T> has to satisfy all constraints on the type parameter for IDocumentPrinter<T>. Thus, you must explicitly say that T satisfies T : IFooDoc.
1: Apparently I need to state this explicitly. If you don't do something the language specification requires you to do, your code won't compile. The language specification requires that when you parameterize a generic type, the type that you parameterize it with must satisfy all the constraints on that type parameter for that generic type. If you do not, your code won't compile.
properties/methods of IFooDoc instances are no longer available within the methods of the Document printer
Well, it's kind of irrelevant what IntelliSense tells you when your code doesn't compile.
If you want to implement IDocumentPrinter<T>, you have to satisfy its constraint. If you don't, your code won't compile.

Generic with multiple constraints

I'm trying to call a method with a definition similar to the following (simplified to avoid confusion):
public static void Register<T>(T value) where T : BaseClass, IInterface
This works fine so long as I have a class instance that defines both of those values. The problem occurs when I pass a `BaseClass' into a method and then try to use that instance in the above declaration. For example:
public class MyClass
{
public MyClass(BaseClass value)
{
Register(value);
}
}
I can pass and instance of a class that implements both BaseClass and IInterface into the constructor, but when I try to use that value in the Register method I get a compilation error stating:
The type 'BaseClass' cannot be used as type parameter 'T' in the generic type or method 'Register(T)'. There is no implicit reference conversion from 'BaseClass' to 'IInterface'.
If I change the type in the constructor like so:
public class MyClass
{
public MyClass(IInterface value)
{
Register(value);
}
}
I get an error stating:
The type 'IInterface' cannot be used as type parameter 'T' in the generic type or method 'Register(T)'. There is no implicit reference conversion from 'IInterface' to 'BaseClass'.
This seems like a bit of a catch-22. Is there a way that I can define the parameter to indicate that it must implement both BaseClass and IInterface?
As I was writing the question I came up with the answer and thought I would post it instead of deleting the question.
I just need to redefine the class:
public class MyClass<T> where T : BaseClass, IInterface
{
public MyClass(T value)
{
Register(value);
}
}
The solution given by Matt is an easy answer for situations where it is not necessary to store the passed-in object in a field or collection. If you need to persist the passed-in object, things get much harder. It's easy for a SomeClass<T> where T meets multiple constraints, to store items of class T and pass them as generics to routines with such constraints, but if a class has a method SomeMethod<TParam>(TParam thing) where TParam:IFoo,BaseBar, it won't have any field of type TParam. It could store thing into a field of type IFoo or BaseBar, but unless BaseBar implements IFoo, or all passed-in instances are going to derive from one particular BaseBar derivative which implements IFoo, there's no way to specify that a field's type will meet both constraints (if every instance does derive from one particular BaseBar derivative which implements IFoo, one could simply use that type as a single constraint, or for that matter not bother with generics at all—just use that as the parameter type).
There are ways of getting around these issues, either using Reflection, an interface pattern I call ISelf<T>, or some tricky nested callback interfaces. In some cases, though, it may be better to provide alternatives to methods that take double-constrained parameters (have the methods take a parameter of one constraint type, cast it to the other type, and accept the lack of compile-time safety).

Why do generic type restrictions have to be redeclared on descendant types?

In C#, given a generic type such as this:
interface IGenericType<T> where T : new()
And a descendant type, such as:
class GenericTypeImplementation<U> : IGenericType<U>
Why do we need to explicitly restrict the generic type U with all the restrictions of the parent type?
class GenericTypeImplementation<U> : IGenericType<U> where U : new()
Am I right in inferring that the issue is in the compiler computing the union of restrictions?
interface IGenericType<T> where T : new()
interface IGenericType2<T> where T : SomeOtherType
class GenericTypeImplementation<U> : IGenericType<U>, IGenericType2<U>
/* Hypothesis: Compiler can't infer U must be "SomeOtherType + new()" */
In my opinion, the compiler could be smart enough to infer the restrictions theoretically. But it shouldn't be so smart, because a too-smart compiler is sometimes dangerous. Developers always need a clear/explicit definition of everything. See this scenario:
(1) there is an interface IFoo<T> where T : new()
(2) a class Foo<T> : IFoo<T> and the new() constraint is added automatically by the compiler(brilliant!)
(3) the class Foo<T> is a very base class in the whole project, class A<T> : Foo<T>, and then class B<T> : A<T>...
(4) Now another developer can hardly realize there is such a constraint by looking into the definition of the class, he will get weird compiling errors(well that's acceptable). But what if they are invoked by reflection? Sometimes the program is correct, because the data meets the restriction by accident.
The compiler is able to to infer that U must be convertible to SomeOtherType and must have a default constructor. It will generate a compiler error for each constraint:
Error 1 The type 'U' must have a public parameterless constructor in order to use it as parameter 'T' in the generic type or method '....IGenericType<T>'
Error 2 The type 'U' must be convertible to '....SomeOtherType' in order to use it as parameter 'T' in the generic type or method '....IGenericType2<T>'
This will also happen with just one of those interfaces implemented as well. The class must successfully implement both interfaces in order to be compiled:
class GenericTypeImplementation<U> : IGenericType<U>, IGenericType2<U>
where U : SomeOtherType, new()
{...}
or as a non-generic type:
class GenericTypeImplementation : IGenericType<SomeType>, IGenericType2<SomeOtherType>
{...}
To mark a class as implementing an interface is not a way of specifying constraints on the generic type parameters of a class; it is a way of requiring that those constraints exist on a new type parameter or that they be satisfied by a supplied type.
Perhaps you could think of it this way: an interface is a constrained set of classes and a generic class is also a constrained set of classes. A generic interface is a constrained set of generic classes. When you say that a generic class implements a generic interface, you are asking the compiler, "Is this generic class strictly within the set specified by this generic interface?" You are not merely intersecting them as a further constrained set of classes.
Because a generic type restriction is on the type parameter of the defining class (U in your example), from a CLR point of view, that is a different type from the type parameter of the interface.
The type parameter of the class need not be the actual type parameter of the interface. It need not even be a simple type, as in:
class Implementation<T> : IGenericType<List<T>> { /* ... */ }
In this case, the compiler recognizes that List<T> satisfies the constraint, and so no further specification is necessary. But without such knowledge about the generic type parameter, the compiler requires you to declare it explicitly.
It is instructive to compare this to the similar but not identical behaviour of generic methods. As with classes that implement interfaces, the type restrictions must be specified with the declaration. There is one notable exception: if the implementation is explicit. In fact, the compiler will generate an error when you try to re-impose the restrictions.
For example, given an interface
interface ISomething {
void DoIt<T>() where T : new();
}
the two correct ways to implement this interface are:
class OneThing : ISomething {
public void DoIt<T>() where T : new() { }
}
class OtherThing : ISomething {
void ISomething.DoIt<T>() { }
}
Leaving out the constraint in OneThing or iserting it in OtherThing produces a compile-time error. Why do we need the constraint in the first implementation and not in the second one? I'd say for the same reason I mentioned above for type constraints on interfaces: in the first implementation, the type T has no relation to the type parameter on the interface method, so it must be made explicit for the method to match the interface method. In the second implementation, the fact that we explicitly declare the interface means that the type parameter T is the exact same one that was used in the interface.

Categories