Casting generic parameter to and from integer

2020-07-18 02:30发布

问题:

I want to write generic class which is intended to work with built-in types like byte and ushort. In internal computations I need to cast generic type to integer and back to generic type. I found the way to compile such code, for example:

class Test<T> where T : struct, IConvertible
{
    public static T TestFunction(T x)
    {
        int n = Convert.ToInt32(x);
        T result = (T)Convert.ChangeType(n, typeof(T));
        return result;
    }
}

I think that using such conversions may significantly reduce performance, if they are used in computation loops. Is there a better way to make these conversions?

回答1:

int to T conversion is a bit tricky. I think you could use Expression class here.

Test<T> class should can look like that:

class Test<T> where T : struct, IConvertible
{
    private static Func<int, T> _getInt;

    static Test()
    {
        var param = Expression.Parameter(typeof(int), "x");
        UnaryExpression body = Expression.Convert(param, typeof(T));
        _getInt = Expression.Lambda<Func<int, T>>(body, param).Compile();
    }

    public static T TestFunction(T x)
    {
        int n = Convert.ToInt32(x);
        T result = _getInt(n);
        return result;
    }
}

It prepares _getInt = x => (T)x for you in static constructor and uses it later, to convert int to T.



回答2:

After some thinking, I'm happy that thanks to the question and some answers I have resolved an old problem of mine: using operations on generic T:

First the example with Cast (as requested by the OP)

public static class Cast<T, U>
{
    public static readonly Func<T, U> Do;

    static Cast()
    {
        var par1 = Expression.Parameter(typeof(T));

        Do = Expression.Lambda<Func<T, U>>(Expression.Convert(par1, typeof(U)), par1).Compile();
    }
}

And then an example with multiplication:

public static class Multiply<T>
{
    public static readonly Func<T, T, T> Do;

    static Multiply()
    {
        var par1 = Expression.Parameter(typeof(T));
        var par2 = Expression.Parameter(typeof(T));

        Do = Expression.Lambda<Func<T, T, T>>(Expression.Multiply(par1, par2), par1, par2).Compile();
    }
}

The use is quite simple:

int x = Conv<T, int>.Do(someTValue);

in the end a static class is created, with a single field that is a readonly static property named Do that is a delegate that "points" to an operation built with an Expression tree.

The multiplication is similar:

T res = Multiply<T, T>.Do(someTValue1, someTValue2);

The multiplication is somewhat 3x slower in the general case than a direct multiplication (in Release mode, no debugging).

Clearly doing the other operations is simple by starting from Multiplication

(it's interesting that I knew quite well of Expression trees, but I hadn't ever thought of using static classes as "dictionaries" for containing the various types. I always did something like Dictionary<Type, Delegate> instead of letting the .NET "handle" the Dictionary through generic class specialization.)