Stephen Gross
Stephen Gross

Reputation: 5714

Is it possible to invoke a generic argument's method in C#?

In C++, you can invoke method's from a template argument like so:

template<class T> class foo
{
  T t;
  t.foo();
}

But in C#, it looks like this is not possible:

class foo<T>
{
  T t;
  public void foo() { 
    t.foo(); // Generates a compiler error
  }
};

I suppose this probably isn't possible in C#, is it?

Upvotes: 3

Views: 253

Answers (4)

Eric Lippert
Eric Lippert

Reputation: 659956

You have discovered the difference between templates and generics. Though they look similar they are in fact quite different.

A template need be correct only for the type arguments that are actually provided; if you provide a T that does not have a method foo then the compilation fails; if you provide only type arguments that have a foo then compilation succeeds.

By contrast a generic must be correct for any possible T. Since we have no evidence that every possible T will have a method foo then the generic is illegal.

Upvotes: 11

James Michael Hare
James Michael Hare

Reputation: 38397

Yes, if you know that the generic type placeholder T implements a member from a base class or interface, you can constrain the type T to that base class or interface using a where clause.

public interface IFooable
{
    void Foo();
}

// ...

public class Foo<T> where T : IFooable
{
    private T _t;

    // ...

    public void DoFoo()
    {
        _t.Foo();  // works because we constrain T to IFooable.
    }
}

This enables the generic type placeholder T to be treated as an IFooable. If you do not constrain a generic type placeholder in a generic, then it is constrained to object which means only object's members are visible to the generic (that is, you only see members visible to an object reference, but calling any overridden members will call the appropriate override).

Note: This is additionally important because of things like operator overloading (remember that operators are overloaded, not overridden) so if you had code like this:

public bool SomeSuperEqualsChecker<T>(T one, T two)
{
    return one == two;
}

This will always use object's == even if T is string. However, if we had:

public bool SomeSuperEqualsChecker<T>(T one, T two)
{
    // assume proper null checking exists...
    return one.Equals(two);
}

This WOULD work as expected with string because Equals() is overridden, not overloaded.

So, the long and the short is just remember that an unconstrained generic placeholder does represent any type, but the only calls and operations visible are those visible on object.

In addition to interface/base class constraints, there are a few other constraints:

  • new() - Means that the generic type placeholder must have a default constructor
  • class - Means that the generic type placeholder must be a reference type
  • struct - Means that the generic type placeholder must be a value type (enum, primitive, struct, etc)

For example:

public class Foo<T> where T : new()
{
    private T _t = new T(); // can only construct T if have new() constraint
}

public class ValueFoo<T> where T : struct
{
    private T? _t; // to use nullable, T must be value type, constrains with struct
}

public class RefFoo<T> where T : class
{
    private T _t = null;  // can only assign type T to null if ref (or nullable val)
}

Hope this helps.

Upvotes: 9

bobbymcr
bobbymcr

Reputation: 24167

It is possible if you are willing to accept generic type constraints. This means that your generic type must be constrained to derive from some base class or implement some interface(s).

Example:

abstract class SomeBase
{
    public abstract DoSomething();
}

// new() ensures that there is a default constructor to instantiate the class
class Foo<T> where T : SomeBase, new()
{
    T t;

    public Foo()
    {
        this.t = new T();
        this.t.DoSomething(); // allowed because T must derive from SomeBase
    }
}

Upvotes: 3

Matthew Whited
Matthew Whited

Reputation: 22433

You need to add a type constraint to your method.

public interface IFoo {
    void Foo();
}

public class Foo<T> where T : IFoo {
     T t;  
    public void foo() {   
        t.Foo(); // Generates a compiler error  
    }
}

Upvotes: 3

Related Questions