【问题标题】:Valid approach for generic operators通用运算符的有效方法
【发布时间】:2014-08-20 06:36:54
【问题描述】:

我有一个班级Gen<T>,我希望能够比较它们。以下代码无法编译,因为 == 不能应用于 Parent 和 Child。有没有办法使这种比较成为可能,或者这种做法通常是不好的做法?

public class Parent{
    public int x;
}

public class Child:Parent{}

public class Gen<T> 
    where T : Parent 
{
    public T variable;
}

public static class Gen
{
    public static bool operator ==(Gen<Parent> left, Gen<Parent> right){
        if (left.variable.x == right.variable.x)
            return true;
        else
            return false;
    }
}

public void Test()
{
    Gen<Parent> foo = new Gen<Parent>();
    Gen<Child> bar = new Gen<Child>();

    if (foo == bar)
    {
        ...
    }
}

完整的上下文如下:

  1. Gen&lt;T&gt; 等于 ColorSet&lt;T&gt; 其中 T:Color
  2. 父级等于颜色
  3. Child 是一个存储颜色附加信息的类,并非每个 Color 对象都需要这些信息。

我想通过ColorSet&lt;T&gt; 类访问每个Color,如下所示:

public class ColorSet<T> where T : Color
{
     private T blue;
     private T red;
     private T green;

     public ColorSet()
     {
         Red = (T)Activator.CreateInstance(typeof(T), new object[] { });
         Red.Name = Values.Res.get("red");
         Blue = (T)Activator.CreateInstance(typeof(T), new object[] { });
         Blue.Name = Values.Res.get("blue");
         Green = (T)Activator.CreateInstance(typeof(T), new object[] { });
         Green.Name = Values.Res.get("green");
     }
}

但有时我需要ColorSet&lt;Color&gt; 有时需要ColorSet&lt;Child&gt; 以获取更多信息。并且应该可以将ColorSet&lt;Color&gt;ColorSet&lt;Child&gt; 进行比较,因为它们具有最相关的共同信息。

【问题讨论】:

  • 我不知道有什么办法——运算符本身不能是通用的——但我也不记得曾经想做这样的事情。老实说,我会写一个简单的方法。
  • @JonSkeet 你的意思是这样的方法: public static bool compare(Gen one, Gen two){...}
  • 是的,尽管遵循正常的命名约定。或使其成为扩展方法。或者使它成为Gen&lt;T&gt; 中的实例方法。但即使有了所有这些,这仍然是一件有点奇怪的事情——比较两种不同类型的值是否相等并不是一个好主意。这里的真实背景是什么?
  • @JonSkeet 感谢您的回复。我会考虑你的建议。我编辑了问题并解释了完整的上下文。如果您需要更多信息,请告诉我。
  • 在这种情况下,我可能会在ColorSet 中添加类似ColorEquals 的内容,并仔细记录它只是比较它们的基本Color 值。然后该方法可以是通用的,接受ColorSet&lt;TOther&gt;

标签: c# generics inheritance operator-keyword


【解决方案1】:

(从 cmets 扩展)似乎不需要泛型类。让运算符为泛型类型工作的一种有效方法是重新设计类型,使它们不再是泛型的。

ColorSet 可以定义为

public class ColorSet {
  private Color red;
  private Color green;
  private Color blue;

  protected ColorSet(Type type) {
    red = (Color)Activator.CreateType(type);
    red.Name = Values.Res.get("red");
    green = (Color)Activator.CreateType(type);
    green.Name = Values.Res.get("red");
    blue = (Color)Activator.CreateType(type);
    blue.Name = Values.Res.get("red");
  }

  public static ColorSet FromType<T>() where T : Color {
    return new ColorSet(typeof(T));
  }
}

您现在可以调用ColorSet.FromType&lt;ExtendedColor&gt;(),而不是new ColorSet&lt;ExtendedColor&gt;()

只要您实际上不需要在构造函数之外使用您的T,此方法就可以工作。

例如,如果你有一个

public T Red { get { return red; } }

属性,您需要将其更改为

public Color Red { get { return red; } }

财产。

但是,如果您有类似的东西,并且确实想保留泛型类型,则可以将其放入派生的泛型类中:

public class ColorSet<T> : ColorSet where T : Color {
  public ColorSet<T>() : base(typeof(T)) { }
  public new T Red { get { return (T)base.Red; } }
}

它仍然只需要基本非泛型 ColorSet 类的运算符。

【讨论】:

  • 我认为这是我的解决方案。非常感谢你。这种方法的唯一(非常非常小的)缺点是,创建一个新的ColorSet 对象与通常情况略有不同。但这是可以接受的。
  • @jasdefer 很高兴它有所帮助,但我认为您错过了我的答案的结尾,这是您问题的 cmets 中没有的内容:您可以创建一个派生的 ColorSet&lt;T&gt; 泛型类来自非泛型 ColorSet 类,并且确实有一个公共构造函数,并允许以您已经习惯的形式进行构造。
  • 啊,是的,你是对的。如果我真的需要 public T Red... 属性,我以为你是这个意思。但它也支持另一种启动新对象的形式。我想我不需要这个,所以创建这个子类只是为了启动目的是没有必要的。再次感谢您。
  • 我刚刚注意到:应该是public new T Red { get { return (T)base.Red; } } 和一个额外的new
  • @jasdefer 不是严格要求,但强烈推荐。谢谢,已修复。
【解决方案2】:

回到您最初的问题/示例:这并不漂亮,但它有效(对于您的示例 - 我只测试了两个) 虽然它使用反射,所以我对它不太满意:

public class Parent
{
    public int x;

    public Parent (int x)
    {
        this.x = x;
    }

    public override bool Equals(object o)
    {
        var p = o as Parent;
        if (object.Equals(p, null))
            return false;

        return this.x == p.x;
    }

    public override int GetHashCode()
    {
        return x;
    }

    public static bool operator ==(Parent a, Parent b)
    {
        return a.Equals (b);
    }

    public static bool operator !=(Parent a, Parent b)
    {
        return !(a == b);
    }

}

public class Child : Parent
{
    public Child (int x)
        : base(x)
    {

    }
}

public class Gen<T> 
    where T : Parent 
{
    public T variable;

    public Gen (T x)
    {
        this.variable = x;
    }

    public override bool Equals(object o)
    {
        if (object.Equal(o, null)) return false;

        // CAUTION: VERY DIRTY - just a quick reply to hvd - should check/remove this with test cases!
        try
        {
           var oT = o.GetType ().GetGenericTypeDefinition ();
           var tT = this.GetType ().GetGenericTypeDefinition ();
           if (tT != oT)
               return false;

           // for example this:
           // var oVar = o.GetType().GetField ("variable").GetValue (o);
           // should really be
           var varField = o.GetType().GetField("variable");
           if (varField == null) return;
           var oVar = varField.GetValue(o);

           if (object.Equals(oVar, null)) 
              return object.Equals(this.variable, null);

           return this.variable.Equals (oVar);
         } catch { return false; }
    }

    public override int GetHashCode()
    {
        return variable.GetHashCode();
    }

    public static bool operator ==(Gen<T> a, object b)
    {
        return a.Equals (b);
    }

    public static bool operator !=(Gen<T> a, object b)
    {
        return !(a == b);
    }

}

这是你的另一个例子:

public static void Test()
{
    Gen<Parent> foo = new Gen<Parent>(new Parent(5));
    Gen<Child> bar = new Gen<Child>(new Child(5));
    Gen<Child> bas = new Gen<Child>(new Child(6));

    if (foo == bar)
        Console.WriteLine ("equal");
    else
        Console.WriteLine ("not-equal");

    if (foo == bas)
        Console.WriteLine ("equal");
    else
        Console.WriteLine ("not-equal");
}

顺便说一句:你真的不需要 Parent 类上的 (==) 和 (!=) - 但它并没有什么坏处

【讨论】:

  • 这种方法可能是可行的(尽管我个人不会这样做),但是有很多情况会在您的表单中抛出一个真正不应该的异常。 o 可以是 nullo 可能有非泛型类型。 o 可以有一个成员 variable,即 nullthis.variable 在您的代码中也可以是 null,尽管在 OP 的情况下不是 ColorSet
  • 是的,一切都是正确的——但如果你真的想要,你可以解决所有这些问题(实际上,一个非常肮脏的解决方法是将这个东西包装成一个 try-catch - 这肯定不是首选解决方案(这肯定来自Jon) - 但对于给定的示例,它确实有效......但我想你有一个好点
【解决方案3】:
public class IGen<out T> 
    where T : Parent 
{
    T Variable{ get; }
}

public class Gen<T>
    : IGen<T>
    where T : Parent 
{
    public T Variable {get;set;}

    private static Func<T, T, bool> _equal;

    static Gen()
    {
        var left = Expression.Parameter(typeof(T));
        var right = Expression.Parameter(typeof(T));
        var body = Expression.Equal(left, right);
        var lambda = Expression.Lambda<Func<T, T, bool>>(body, left, right);
        _equal = lambda.Compile();
    }

    public static bool operator ==(Gen<T> left, Gen<T> right)
    {
        return _equal(left.Variable, right.Variable);
    }


    public static bool operator ==(Gen<T> left, IGen<T> right)
    {
        return _equal(left.Variable, right.Variable);
    }


    public static bool operator ==(IGen<T> left, Gen<T> right)
    {
        return _equal(left.Variable, right.Variable);
    }
}

【讨论】:

  • 我不熟悉表达式,但它的工作原理是这样的:如果创建了新的Gen&lt;T&gt;,则会创建新的方法 _equal,它与 T 类型的对象进行比较。但我的编译器是仍然抱怨if (foo == bar) 无效,因为== 不能应用于Gen&lt;Parent&gt;Gen&lt;Child&gt;。但也许我犯了一些错误,因为我找不到表达式(我应该使用System.Linq.Expressions吗?但找不到Arguement
  • @jasdefer 我的错。是凭记忆做的。 Argument 实际上是 Parameter
  • 没问题,感谢您的回复。但是使用这个,我仍然遇到问题,foo == bar 不起作用,因为如果我没有犯另一个错误,== 不能应用于Gen&lt;Parent&gt;Gen&lt;Child&gt;
  • 我不确定这是什么,但这与我最初的问题中的原因相同吗?但是非常感谢您的付出!
  • @jasdefer 问题是左边的T != 右边的T
猜你喜欢
  • 2017-06-02
  • 2019-03-19
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 2020-12-15
  • 2014-08-11
  • 1970-01-01
  • 2020-08-21
相关资源
最近更新 更多