Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

After coming up against this problem myself in trying to implement a generic Vector2<int/float/double> in C#, I've done a bunch of investigation into this problem, also described in this question:

Less generic generics? A possible solution for arithmetic in C# generics

These links contain some more background information and fascinating solution approaches:

https://jonskeet.uk/csharp/miscutil/usage/genericoperators.html

http://www.codeproject.com/KB/cs/genericnumerics.aspx

Now that C# 4.0 is out with its new versatile dynamic type, my question for the brilliant SO community, is this: is it a tool that could be used perhaps to build performant, generic Vector/Matrix/etc. numeric types?

Clearly a Vector2 could be built by simply like so:

public struct Vector2
{
    public dynamic X;
    public dynamic Y;

    public Vector2(dynamic x, dynamic y)
    {
        this.X = x;
        this.Y = y;
    }

    public static Vector2 operator+(Vector2 a, Vector2 b)
    {
        return new Vector2(a.X + b.X, a.Y + b.Y);
    }
}

but with this approach we have no type constraint here, so you could make a Vector2(3, 12.4572). Is there a way that we could mix dynamic members with a type parameter Vector2<int> to perform our math operations as would be done with ints?

Perhaps some form of casting could be used to ensure this.X is a T, though I don't know how that would perform.

like image 928
Collin Arnold Avatar asked Feb 16 '11 07:02

Collin Arnold


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.


1 Answers

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));
    }
}
like image 145
Ani Avatar answered Oct 12 '22 23:10

Ani