简体   繁体   中英

C# generic class calling method overloads with primitives

I've used templates in C++ but haven't really tried to get too fancy with C# generics in the past. Here's a simplified cut-down version of what I'm trying to do (this would be possible in C++):

class DoesStuffWithPrimatives
{
    public void DoStuff(double value) { }
    public void DoStuff(string value) { }
    public void DoStuff(int value) { }
    public void DoStuff(uint value) { }
    // etc...
}

class GenericBase<T>
{
    private readonly T _testValue;
    private DoesStuffWithPrimatives _doesStuff = new DoesStuffWithPrimatives();
    public GenericBase(T testValue)
    {
        _testValue = testValue;
    }

    public void DoStuff()
    {
        _doesStuff.DoStuff(_testValue);
    }
}

class DoubleContrete : GenericBase<double>
{
    public DoubleContrete() : base(1.54545487)
    {
    }
}

class IntConrete : GenericBase<int>
{
    public IntConrete() : base(80085)
    {
    }
}

I get the following compile error (in the DoStuff() method on GenericBase<T> ):

Error CS1503 Argument 1: cannot convert from 'T' to 'double'

Why can't the compiler resolve which of the DoesStuffWithPrimatives.DoStuff(…) overloads to call!?

With generics, the best that the compiler can do is assume that the type parameter ( T in this case) can be any type with a base of what you've specified. Since you didn't specify a base, The compiler treats T as anything that inherits from Object , which is literally anything.

The compiler can't decide that your T is a double because that wouldn't make sense for every type of T that isn't a double . Take, for example, the following generic method:

public void DoStuff<T>(T param)
{
    DoStuffWithDouble(param);
}

When T is double, then this would work fine, as you can just substitute double for T :

public void DoStuff(double param)
{
    DoStuffWithDouble(param); // param is a double, so no problem
}

However, T could be something else like, say, a List . In this case, this code wouldn't compile:

public void DoStuff(List param)
{
    DoStuffWithDouble(param); // param is not double, this wouldn't compile
}

The compiler cannot make the assumption that T is double because doing so would break everywhere that T is not double.

You can cast it, of course, as well as perform type checks on the object.

public void DoStuff<T>(T param)
{
    if (param is double)
        // Only runs if T is confirmed to be a double, so no chance for errors
        DoStuffWithDouble((double)param); 
}

The reason why the code does not work is that T is not limited to a fixed set of types. T can be any type: struct, class, delegate, interface...

When you do this:

public void DoStuff()
{
    _doesStuff.DoStuff(_testValue);
}

It of course will not compile because _testValue can be anything, IEnumerable , Button , XmlSerializer , Dictionary<int, int> , CultureInfo or even some types you created yourself, just to name a few of the extreme ones. Obviously, you can never pass a CultureInfo to a method that accepts a double or an int .

The error message is just the compiler's way of saying T can be any type, not neccessarily double or int .

One of the solutions to this problem is simple, just don't use generics at all! In my opinion, I don't think you should use generics in this situtation.

Generics should be used when you care little about (or don't care at all) what type is used. For example, List<T> does not care about what type it is storing. It works with any type. Some other classes/interfaces/methods work with types that has specific attributes: implement certain interfaces, has a default constructor, is a class etc. That's why we have generic type constraints. However, there are always a infinite set of types that fulfills these constraints. You can always create a type that implements certain interfaces or has a default constructor. There does not exist generic constraints that constraint the generic type to a finite number of types, like the "primitives" as far as I'm concerned.

As stated, the compiler can't know for certain that T is a valid type for any of the overloads. However it is possible to use reflection to get the proper method. Without proper handling this could cause runtime errors of course and has some overhead. The overhead can be largely negated if it's possible to have a static variable. In the example below the Action<T> 'dostuffer' will be determined once for each type when it is used:

class GenericBase<T>
{
    private readonly T _testValue;

    public GenericBase(T testValue)
    {
        _testValue = testValue;
    }


    static readonly DoesStuffWithPrimatives _doesStuff = new DoesStuffWithPrimatives();
    static readonly Action<T> dostuffer = (Action<T>)Delegate.CreateDelegate(typeof(Action<T>),_doesStuff,
        typeof(DoesStuffWithPrimatives).GetMethod(nameof(DoesStuffWithPrimatives.DoStuff), new[]{typeof(T)}));


    public void DoStuff()
    {
        dostuffer(_testValue);
    }
}

If the DoesStuffWithPrimatives class can't be static (if it can consist of several different variables), the static variable should be the method info and the instance constructor of GenericBase should handle the CreateDelegate, using the DoesStuffWithPrimatives variable

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM