开发者

Generic C# Code and the Plus Operator [duplicate]

This question already has answers here: Is there a constraint that restricts my generic method to numeric types? (24 answers) Closed 8 years ago.

I'm writing a class that does essentially the same type of calculation for each of the primitive numeric types in C#. Though the real calculation is more complex, think of it as a method to compute the average of a number of values, e.g.

class Calc
{
    public int Count { get; private set; }
    public int Total { get; private set; }
    public int Average { get { return Count / Total; } }
    public int AddDataPoint(int data)
    {
        Total += data;
        Count++;
    }
}

Now to support that same operation for double, float and perhaps other classes that define operator + and operator /, my first thought was to simply use generics:

class Calc<T>
{
    public T Count { get; private set; }
    public T Total { get开发者_JAVA百科; private set; }
    public T Average { get { return Count / Total; } }
    public T AddDataPoint(T data)
    {
        Total += data;
        Count++;
    }
}

Unfortunately C# is unable to determine whether T supports operators + and / so does not compile the above snippet. My next thought was to constrain T to types that support those operators, but my initial research indicates this cannot be done.

It's certainly possible to box each of the types I want to support in a class that implements a custom interface e.g. IMath and restrict T to that, but this code will be called a great number of times and I want to avoid boxing overhead.

Is there an elegant and efficient way to solve this without code duplication?


I ended up using Expressions, an approach outlined by Marc Gravell that I found by following links off of spinon's comment.

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


(excuse me if I post it today, but I was looking for a place where to put this piece of code, and this question seemed to be perfect)

As an extension on the Gravell's article:

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

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

        var add = Expression.Add(par1, par2);

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

You use it like:

int sum = Add<int>.Do(x, y);

The advantage is that we use the type system of .NET for safekeeping the various "variants" of Add and creating new ones if necessary. So the first time you call Add<int>.Do(...) the Expression will be built, but if you call it a second time, the Add<int> will already be fully initialized.

On some simple benchmark, it's 2x slower than direct addition. I think it's very good. Ah... it's compatible with objects that redefine the operator+. Clearly building the other operations is easy.

Addition from Meirion Hughes

Method can be extended with meta-coding so you can handle cases of T1 operation T2. For instance, here if T1 is a number, then it needs to be converted to T2 == double first before the operator * then converts it back. Whereas when T1 is Foo and Foo has operator to multiply with a T2 == double you can omit the conversion. The try, catch is necessary because it is the easiest way to check if the T operator *(T, double) is present.

public static class Scale<T>
{
    public static Func<T, double, T> Do { get; private set; }

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

        try
        {
            Do = Expression
                .Lambda<Func<T, double, T>>(
                    Expression.Multiply(par1, par2),
                    par1, par2)
                .Compile();
        }
        catch
        {
            Do = Expression
                .Lambda<Func<T, double, T>>(
                    Expression.Convert(
                        Expression.Multiply(
                            Expression.Convert(par1, typeof (double)),
                            par2),
                        typeof(T)),
                    par1, par2)
                .Compile();
        }
    }
}


There is an approach using dynamic in C# 4.0, it is not perfect obviously but it can bring a new light to the matter.

Details are in this blog post


I found another interesting approach, which is easier to code and debug than the expression tree solution I originally used:

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

This solution uses generic type constraints in an interesting way to ensure all required operations are supported, but without introducing any boxing or virtual method calls.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜