web-dev-qa-db-ja.com

C#ジェネリック演算子

私は次のようなジェネリック演算子を実装しようとしています:

class Foo
{
   public static T operator +<T>(T a, T b) 
   {
       // Do something with a and b that makes sense for operator + here
   }
}

本当に私がやろうとしているのは、継承を優雅に処理することです。 Fooの標準演算子+を使用すると、Tは代わりに「Foo」になります。誰かがFooから派生している場合(たとえば、BarがFooを継承している場合)、Bar + Bar操作は引き続きFooを返します。私はこれを汎用演算子+で解決したいと思っていましたが、上記の構文エラー(<)が表示され、そのようなコードは合法ではないと思われます。

ジェネリック演算子を作成する方法はありますか?

22
Shirik

いいえ、C#でジェネリック演算子を宣言することはできません。

演算子と継承は実際にはうまく混ざりません。

Foo + FooでFooを返し、Bar + BarでBarを返す場合は、クラスごとに1つの演算子を定義する必要があります。ただし、演​​算子は静的であるため、呼び出す演算子はコンパイル時に決定されるため、ポリモーフィズムのメリットは得られません。

Foo x = new Bar();
Foo y = new Bar();
var z = x + y; // calls Foo.operator+;
19

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

static T Add<T>(T a, T b) {
    //TODO: re-use delegate!
    // declare the parameters
    ParameterExpression paramA = Expression.Parameter(typeof(T), "a"),
        paramB = Expression.Parameter(typeof(T), "b");
    // add the parameters together
    BinaryExpression body = Expression.Add(paramA, paramB);
    // compile it
    Func<T, T, T> add = Expression.Lambda<Func<T, T, T>>(body, paramA, paramB).Compile();
    // call it
    return add(a,b);       
}
6
Rakoo

ジェネリッククラスFooで演算子を定義するだけです。

実際の汎用演算子を作成することもできますが、C#コンパイラはそれらを使用しません。

[System.Runtime.CompilerServices.SpecialName]
public static T op_Addition<T>(T a, T b) { ... }
3
Ark-kun

C#でジェネリック演算子を宣言することはできません-理由はわかりませんが、実装チームにとっては有用性と労力の関係であると思います(Jon Skeetがそれについて議論している投稿があるか、彼のブログにあるかもしれません)彼がC#で見たいことについて話し合った)。

実際、C#のジェネリックスで演算子useを使用することもできません。

これは、ジェネリックが提供される可能性のあるすべてのタイプに適用可能でなければならないためです。これが、以下のように==を使用する場合に、ジェネリック型をクラスにスコープする必要がある理由です。

void IsEqual<T>(T x, T y) where T : class
{
    return x == y;
}

残念ながら、次のことはできません。

void Add<T>(T x, T y)  where T : operator +
{
    return x + y;
}

あなたも興味があるかもしれません この短い要約記事 私は出くわしました。

2
Matt Mitchell

同じことを探していて、グーグルが私をここに連れてきました....私は受け入れられた答えにあまり満足していなかったので、回避策を探していました。

私はジェネリックを使用してこれを実装することができました。 Foo andBarクラスは次のとおりです。

    class Foo
    {
        private int value;

        public Foo(int x)
        {
            value = x;
        }
        public virtual int getVal()
        {
            return value;
        }
    }

    class Bar : Foo
    {
        private int derivedValue;

        public Bar(int x):base(x) 
        {
            derivedValue = x;
        }
        public override int getVal()
        {
            return derivedValue;
        }
    }

次に、演算子を含むがFooのタイプに制限され、Fooから派生したジェネリッククラス:

    class GenericOp<T> where T : Foo
    {
        private T value;

        public GenericOp(T x)
        {
            value = x;
        }
        public static Foo operator +(GenericOp<T> a, GenericOp<T> b) 
        {
            return new Foo(a.value.getVal() + b.value.getVal());
        }
    }

常にFooを取り戻すだけでなく、タイプの混合を防ぐことを示すいくつかの使用法コード:

Foo f1 = new Foo(1);
Foo f2 = new Foo(2);
Bar b1 = new Bar(10);
Bar b2 = new Bar(20);

GenericOp<Foo> left = new GenericOp<Foo>(f1);
GenericOp<Foo> right = new GenericOp<Foo>(f2);
Foo res = left + right;

GenericOp<Bar> left1 = new GenericOp<Bar>(b1);
GenericOp<Bar> right1 = new GenericOp<Bar>(b2);
Foo res1 = left1 + right1;

GenericOp<Foo> left2 = new GenericOp<Foo>(f1);
GenericOp<Bar> right2 = new GenericOp<Bar>(b1);
//Foo res2 = left2 + right2; //this fails and rightfully so.
1
tomatoRadar