Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can a `System.Type` be a generic parameter with constraints?

How can a System.Type be a generic parameter with constraints?

Here interface I has a single method M which classes C and D implement.

public interface I {
    void M;
}

public class C: I {
    public void M();
}
public class D: I {
    public void M();
}

Service instantiates a new T() (which is really C/D implementing I) and runs I interface methods on it

public class Service {
    public void Fn<T>(T t): where T : Type, I, new() {
        ( new t() ).M();
        // ...
    }
}

This will eventually be the public API for the service so I want it to be as concise as possible:

public class Main {
    public void main() {
        Service.Fn( typeof( C ));
        Service.Fn( typeof( D ));
        // ...
    }
}

This isn't compiling and the error is: 'System.Type' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method ...

The ultimate goal here is to be able to call Service.Fn concisely, as described in Main. Any alternatives to my interfaces attempt are also welcome.

like image 727
xst Avatar asked Oct 22 '25 22:10

xst


2 Answers

The following code works for me:

public class Service {
    public void Fn<T>() where T : I, new() {
        (new T()).M();
        // ...
    }
}
public interface I
{
    void M();
}

public class C : I
{
    public void M();
}
public class D : I
{
    public void M();
}

static void Main()
{
    var service = new Service();
    service.Fn<C>();
    service.Fn<D>();
}

The issue was the superfluous Type in the where clause. T is a Type anyway by the fact it is a generic parameter - but an object of type A or B wouldn't be castable to the Type class - this is what where T : Type would mean.

In general, where T: X, Y, Z means that any T must (depending on what X, Y and Z are) either implement the interface, or be a subclass of, X, Y and Z. The new() constraint is slightly different, and makes the compiler aware that you also wish to be able to create a new object of type T in the method. (see http://msdn.microsoft.com/en-gb/library/bb384067.aspx and http://msdn.microsoft.com/en-gb/library/d5x73970.aspx for more details)

Also, I've removed typeOf() from your input, as when you're using a generic the type can be referenced directly in the function (see above). Hope that makes sense. Any questions, just ask!

like image 112
David E Avatar answered Oct 24 '25 11:10

David E


This example may help you understand what's going on here:

void PrintTypeName(Type t) { Console.WriteLine(t.Name); }
void PrintTypeName<T>() { Console.WriteLine(typeof(T).Name); }

void Main()
{
    Type cType = typeof(C);
    Type dType = typeof(D);
    C cInstance = new C();
    D dInstance = new D();

    PrintNameOfType(cType.GetType());     //prints "RuntimeType"
    PrintNameOfType(dType.GetType());     //prints "RuntimeType"
    PrintNameOfType(cInstance.GetType()); //prints "C"
    PrintNameOfType(dInstance.GetType()); //prints "D"

    PrintNameOfType(cType);     //prints "C"
    PrintNameOfType(dType);     //prints "D"

    //does not compile:
    //PrintNameOfType(cInstance);
    //PrintNameOfType(dInstance);

    PrintNameOfType<Type>(); //prints "Type"
    PrintNameOfType<C>();    //prints "C"
    PrintNameOfType<D>();    //prints "D"
}
like image 44
phoog Avatar answered Oct 24 '25 13:10

phoog