C# 4.0 dynamic: A potential performant solution to numeric generics?

后端 未结 1 730
星月不相逢
星月不相逢 2021-02-08 23:26

After coming up against this problem myself in trying to implement a generic Vector2 in C#, I\'ve done a bunch of investigation into this pr

相关标签:
1条回答
  • 2021-02-08 23:48

    Only you can tell if dynamic operator invocations will meet your performance requirements, but it's certainly possible to address some of your type-safety concerns with generics - there's no reason that everything has to be checked at run-time just because of one little dynamic call:

    // Consider making this type immutable
    public struct Vector2<T>
    {
        public T X;
        public T Y;
    
        public Vector2(T x, T y)
        {
            this.X = x;
            this.Y = y;
        }
    
        //  The only dangerous operation on the type
        public static Vector2<T> operator +(Vector2<T> a, Vector2<T> b)
        {
            return new Vector2<T>((dynamic)a.X + b.X, (dynamic)a.Y + b.Y);
        }
    }
    

    Now, the only dangerous operation is to actually add 2 vectors of the same type (the addition operator needs to work as expected on the type-argument), but everything else is perfectly type-safe, as it should be. You can't do new Vector<int>("a", 5), add a Vector<int> and a Vector<string>, or assign the addition of two Vector<int>s to a Vector<string>. Note that none of these errors would have been caught at compile-time with your original solution.

    Note that:

    1. There's nothing to stop you from using generics here but going down the compiling-an-expression-tree route for the addition instead of dynamic. Delegate invocations aren't free, but they should in theory be faster than the dynamic approach in this case - at the very least, you avoid boxing value-types. Only you can tell if they will be fast enough, though.

    2. In all cases, consider writing a static-constructor that validates that the type-argument in fact has a suitable addition operator, so that type-errors happen early on in the game.


    EDIT (OP isn't satisfied with the performance of dynamic here):

    The expression-tree approach would look something like:

    public struct Vector2<T>
    {
        private static readonly Func<T, T, T> Add;
    
        // Create and cache adder delegate in the static constructor.
        // Will throw a TypeInitializationException
        // if you can't add Ts or if T + T != T 
        static Vector2()
        {
            var firstOperand = Expression.Parameter(typeof(T), "x");
            var secondOperand = Expression.Parameter(typeof(T), "y");
            var body = Expression.Add(firstOperand, secondOperand);
            Add = Expression.Lambda<Func<T, T, T>>
                  (body, firstOperand, secondOperand).Compile();
        }
    
        public T X;
        public T Y;
    
        public Vector2(T x, T y)
        {
            this.X = x;
            this.Y = y;
        }
    
        public static Vector2<T> operator +(Vector2<T> a, Vector2<T> b)
        {
            // Delegate invocation instead of dynamic operator invocation.
            return new Vector2<T>(Add(a.X, b.X), Add(a.Y, b.Y));
        }
    }
    
    0 讨论(0)
提交回复
热议问题