【问题标题】:Compile error using an interface as a generic type constraint使用接口作为泛型类型约束编译错误
【发布时间】:2014-09-23 01:57:37
【问题描述】:

更新:

我会接受让代码工作Jon's answer to this other question 是正确的。 this answer 回答了为什么接口被视为引用类型。

我还是想知道有什么区别。当接口是泛型类型约束时,为什么不将接口视为引用类型?这是他们的一些设计原因吗?我有一种感觉,唯一的原因可能是“他们就是这样”。

原问题:

我正在将一个类转换为泛型类,但我发现使用接口作为类型参数时出现了一些奇怪的行为。该类有一个字段和属性,它们是一种接口类型,例如 IMagic。

public interface IMagic
{
    bool Magic { get; }
}

public class HasMagic
{
    private IMagic _magic;
    public IMagic Magic
    {
        get { return _magic; }
        set
        {
            if (value != _magic)
                _magic = value;
        }
    }

    public bool IsMagical
    {
        get { return _magic != null ? _magic.Magic : true; }
    }
}

我想将它们改为类型 T 并使用类型参数 where T : IMagic 定义类。这样做虽然给了我一个编译器错误Operator '!=' cannot be applied to operands of type 'T' and 'T'

public class HasMagic<T> where T : IMagic
{
    private T _magic;
    public T Magic
    {
        get { return _magic; }
        set
        {
            // Compiler error here!
            if (value != _magic)
                _magic = value;
        }
    }

    public bool IsMagical
    {
        // But no error here!?
        get { return _magic != null ? _magic.Magic : true; }
    }
}

那么为什么通用版本不起作用? ==!= 运算符不应该适用于所有类型吗?

该错误只发生在属性设置器中,所以它让我想到,_magic 字段实际上是盒装的 IMagic 还是其他一些引用类型?事实上,它可以设置为 null ,这应该只适用于可为空的类型。正如下面的测试所示,IMagic 结构 (MagicStruct) 工作正常,但为什么呢?如您所料,将 HasMagic 中的字段和属性更改为 MagicStruct 会产生编译错误。

public class MagicTests
{
    [Fact]
    public void SomeMagicTest()
    {
        var mag = new HasMagic();
        Assert.True(mag.IsMagical);

        mag.Magic = new MagicClass();
        Assert.False(mag.IsMagical);

        mag.Magic = new MagicStruct();
        Assert.True(mag.IsMagical);

        mag.Magic = null;
        Assert.True(mag.IsMagical);
    }
}

public class MagicClass : IMagic
{
    public bool Magic { get { return false; } }
}

public struct MagicStruct : IMagic
{
    public bool Magic { get { return true; } }
}

如果有任何相关性,我正在使用 .Net framework v4.5.2。

【问题讨论】:

  • “那么为什么通用版本不起作用?== 和 != 运算符不应该适用于所有类型吗?”见stackoverflow.com/questions/390900/…
  • 1:T 可能有也可能没有定义==!= 运算符。 2:if (value != _magic)是多余的
  • @IlyaLuzyanin 我明白这一点。但是如果只使用应该有同样问题的接口,它就可以正常工作。

标签: c# generics


【解决方案1】:

T 是一个类型参数,可以是类或结构,因为编译器不允许您执行类或结构中不存在的操作。你可以这样试试:

public T Magic
    {
        get { return _magic; }
        set
        {
            // Compiler error here!
              if (!EqualityComparer<T>.Default.Equals(_magic, value))
                _magic = value;
        }
    }

或者你可以在你的代码中使用 Equals

 public T Magic
    {
        get { return _magic; }
        set
        {
            // Compiler error here!
            if (!value.Equals(_magic))
                _magic = value;
        }
    }

【讨论】:

  • @kjbartel 因为您没有使用泛型类型。你可以在这篇文章中看到更多关于为什么你不能使用操作符的信息:stackoverflow.com/questions/390900/…
  • EqualityComparer 版本可以工作,但 Equals 不能,因为如果值为 null,您可以获得 NullReferenceException。我仍然很好奇接口版本发生了什么,尽管它显然被视为引用类型。
  • @kjbartel 你的另一个问题在stackoverflow中得到了回答:stackoverflow.com/questions/4638367/…
【解决方案2】:

您不能在泛型类型上使用!= (==)。

一个可能的解决方案是IMagic 来实现IComparable

public interface IMagic : IComparable

然后使用CompareTo

 if (value.CompareTo(_magic) != 0)

您可以编写自己的 CompareTo 方法实现

 public int CompareTo(object obj) {

    }

更新

如果您无法编辑 IMagic,请尝试像这样向 HasMagic 类添加属性

public Func<T, T, bool> FuncEvaluate { get; set; } 

然后这样检查

  public T Magic
    {
        get { return _magic; }
        set
        {
            if (FuncEvaluate != null)
            {
                if (!FuncEvaluate(value, _magic))
                {
                    _magic = value;
                }
            }
            else
            {
                throw new NotImplementedException();
            }
        }
    }

当你决定使用该类时,你应该设置函数来比较对象,也许检查typeof(T).IsValueType

【讨论】:

  • 很遗憾,我无法更改界面,因为我无法更改。我实际上正在使用 INotifyPropertyChanged 并且需要同时支持类和结构。
  • 虽然很有趣,但 !EqualityComparer.Default.Equals(_magic, value) 更简单、更干净。还是谢谢。
【解决方案3】:

T 可以是值类型或引用类型;你没有约束它。 !=(和 ==)运算符(引用比较)仅为引用类型定义,而不是为值类型定义。

有几种解决方案;您可以将 T 约束为引用类型:

public class HasMagic<T> where T : IMagic, class

您也可以使用 Equals()。或 object.ReferenceEquals()。 也许还有一些我没有想到的其他解决方案。

编辑:我刚刚注意到你甚至不需要 != 在 setter 中,所以整个问题变得无声了。

【讨论】:

  • OP 在他的示例中使用 struct 所以我怀疑代码解决方案是否相关。
  • 这是一个简短的例子。我在真正的课堂上确实需要 !=。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-08
  • 2020-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多