【问题标题】:C# Generic OperatorsC# 泛型运算符
【发布时间】:2011-08-19 19:39:39
【问题描述】:

我正在尝试像这样实现一个通用运算符:

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。我希望用通用运算符 + 来解决这个问题,但我只是得到了上述(在

有没有办法制作泛型运算符?

【问题讨论】:

  • 一个可行(尽管对程序员不太友好)的解决方法可能是一个采用通用参数的命名方法:public static T Add&lt;T&gt;(T a, T b) { //Implementation goes here }。这可以像Foo x = new Bar(); Foo y = new MyClass(); Foo sum = Foo.Add(x, y); 一样使用。

标签: c# generics operator-overloading


【解决方案1】:

不,您不能在 C# 中声明泛型运算符。

操作符和继承并不能很好地混合。

如果您希望 Foo + Foo 返回一个 Foo 和 Bar + Bar 返回一个 Bar,则需要在每个类上定义一个运算符。但是,由于运算符是静态的,因此您不会获得多态性的好处,因为调用哪个运算符将在编译时决定:

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

【讨论】:

  • 不幸,但足够简单。谢谢。
  • Shirik,如果这是您问题的答案,您应该接受 Martinho 的回答。
  • 我应该,但由于前 15 分钟我无法接受答案,所以我不会。
  • 我不太确定我是否会购买您的“考虑另一个有问题的场景”。老实说,运营商在任何方面都不是特别的。它们只是名称不寻常的方法。您的问题同样可以更改为我有 3 种方法的情况,这些方法以“Foo,Foo”,“Bar,Bar”或“Qux,Qux”作为参数。会拿哪一个?显然是“Foo,Foo”之一,因为它是唯一满足类型的。但我不需要相信他们为什么选择做出这个限制。我只是接受这是一种限制并继续前进。
  • @Shirik:是的,你说得对。这个例子更像是一个糟糕的设计,而不是操作员的问题。删除它。
【解决方案2】:

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);       
}

【讨论】:

  • 谈论不可读。如此多的抽象,很少有意图(暴露)。可维护性和可重用性不一样重要吗?
  • @barrypicker,强烈同意,我确实认为可维护性是第一位的。代码就是代码,对于人类来说,首先必须是可读的
【解决方案3】:

您可以只在泛型类 Foo 中定义运算符。

您也可以创建真正的泛型运算符,但 C# 编译器不会使用它们。

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

【讨论】:

  • C# 编译器也不会使用非泛型
  • @zzfima 你是什么意思?
【解决方案4】:

您不能在 C# 中声明泛型运算符 - 我不确定其中的原因,但假设这对实现团队来说是有用还是努力的事情(我相信这里可能有 Jon Skeet 讨论它的帖子,或者可能在他的当他讨论他想在 C# 中看到的东西时写博客)。

事实上,你甚至不能在 C# 中使用运算符和泛型。

这是因为泛型必须适用于所有可能提供的类型。这就是为什么当你想使用== 时必须将泛型类型限定为类,如下所示:

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;
}

您可能也对我遇到的this short summary article 感兴趣。

【讨论】:

  • 但是,您可以将后者的return x + y; 位包装在一个抽象方法中,强制孩子实现它。这仍然不理想,因为它会保留父类的输入,而不是子类的输入。
【解决方案5】:

正在寻找同样的东西,谷歌把我带到了这里.... 我对接受的答案不太满意,正在寻找解决方法。

我设法使用泛型来实现这一点。这是 Foo 和 Bar 类:

    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.

【讨论】:

  • 这基本上不只是对特定类型进行包装吗?原始帖子描述的是一个完整的通用解决方案,而不是仅限于狭窄类型的解决方案。让Foo 使用构造函数重载并在构造上进行数学运算会减少很多代码,更易于维护,并且更便宜。使用这种方法,您可以避免为每个结果创建额外的对象并避免整个类。
  • @AWinkle: 不是真的......原来的帖子是寻找对一个类型和从该类型派生的类的操作。正如 R. Martinho Fernandes 所提到的:“由于运算符是静态的,因此您不会获得多态性的好处,因为调用哪个运算符将在编译时决定。”而在这里,您总是可以在运行时返回您想要的类型.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-10
  • 1970-01-01
  • 1970-01-01
  • 2015-05-23
  • 2014-02-06
  • 2014-01-01
相关资源
最近更新 更多