对于expr == null和expr!= null

时间:2016-06-01 08:02:05

标签: c# .net null

我看到一些非常奇怪的东西,我无法解释。我猜测一些我不熟悉的C#边缘情况,或运行时/发射器中的错误?

我有以下方法:

public static bool HistoryMessageExists(DBContext context, string id)
{
    return null != context.GetObject<HistoryMessage>(id);
}

在测试我的应用程序时,我发现它行为不端 - 它返回true我知道在我的数据库中不存在的对象。所以我停止了这个方法,在立即,我运行了以下内容:

context.GetObject<HistoryMessage>(id)
null
null == context.GetObject<HistoryMessage>(id)
true
null != context.GetObject<HistoryMessage>(id)
true

GetObject的定义如下:

public T GetObject<T>(object pk) where T : DBObject, new()
{
    T rv = Connection.Get<T>(pk);

    if (rv != null)
    {
        rv.AttachToContext(this);
        rv.IsInserted = true;
    }

    return rv;
}

有趣的是,在将表达式转换为object时,会正确评估比较:

null == (object)context.GetObject<HistoryMessage>(id)
true
null != (object)context.GetObject<HistoryMessage>(id)
false

没有相等运算符覆盖。

编辑:事实证明存在操作员重载,这是不正确的。但是,为什么在内部方法通用GetObject中正确评估等式,其中rv在这种情况下属于HistoryMessage类型。

public class HistoryMessage : EquatableIdentifiableObject
{
    public static bool HistoryMessageExists(DBContext context, string id)
    {
        var rv = context.GetObject<HistoryMessage>(id);
        bool b = rv != null;
        return b;
    }

    public static void AddHistoryMessage(DBContext context, string id)
    {
        context.InsertObject(new HistoryMessage { Id = id });
    }
}

public abstract partial class EquatableIdentifiableObject : DBObject, IObservableObject
{
    public event PropertyChangedEventHandler PropertyChanged;

    [PrimaryKey]
    public string Id { get; set; }

    //...
}

public abstract partial class EquatableIdentifiableObject
{
    //...

    public static bool operator ==(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
    {
        if (ReferenceEquals(self, null))
        {
            return ReferenceEquals(other, null);
        }

        return self.Equals(other);
    }

    public static bool operator !=(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
    {
        if (ReferenceEquals(self, null))
        {
            return !ReferenceEquals(other, null);
        }

        return !self.Equals(other);
    }
}

public abstract class DBObject
{
    [Ignore]
    protected DBContext Context { get; set; }

    [Ignore]
    internal bool IsInserted { get; set; }

    //...
}

这里发生了什么?

2 个答案:

答案 0 :(得分:3)

  • 正如您已经澄清的那样,==运算符因您的类型失败而导致类型失败。
  • 当转换为对象时,==运算符正常工作,因为object's使用了==而不是EquatableIdentifiableObject's
  • 在方法GetObject中,运算符正确评估,因为它不是EquatableIdentifiableObject's正在使用的==实现。在C#中,泛型在运行时被解析(至少在这里是相关的意义上),而不是在编译时。请注意,==是静态的而不是虚拟的。因此,类型T在运行时被解析,但是必须在编译时解析对==的调用。在编译器解析==的编译时,它将不知道使用EquatableIdentifiableObject's的{​​{1}}实现。由于类型T具有此约束:==,因此将使用where T : DBObject, new()实现(如果有)。如果DBObject's未定义DBObject,则将使用执行此操作的第一个基类(最多==)的实现。

关于object EquatableIdentifiableObject's的实施的更多评论:

  • 您可以替换此部分:
==
  

with:

if (ReferenceEquals(self, null))
{
     return ReferenceEquals(other, null);
}
  • 替换
  • 会更强大
// If both are null, or both are the same instance, return true.
if (object.ReferenceEquals(h1, h2))
{
    return true;
}
  

with:

public static bool operator !=(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
{
    ...
}
  • 您为public static bool operator !=(EquatableIdentifiableObject self, EquatableIdentifiableObject other) { return !(self == other); } 定义签名的方式略有误导。第一个参数名为==,第二个参数名为self。如果other是实例方法,那就没问题。由于它是静态方法,因此名称==有点误导。更好的名称将是selfo1或沿此行的某些东西,以便在更平等的基础上处理这两个操作数。

答案 1 :(得分:1)

您现在知道operator ==(...)可能会有多次重载。其中一些可能是C#内置重载,而其他可以是用户定义的运算符。

如果您将鼠标悬停在Visual Studio中的!===符号上,它将显示过载分辨率选择的过载(直到VS2013,它只会在选定的过载时显示实际上是一个用户定义的,在VS2015中它将在我认为的所有情况下显示它。

== 绑定 (即调用的重载)在编译时静态修复。它没有任何动态或虚拟的东西。所以如果你有:

public T SomeMethod<T>() where T : SomeBaseClass
{
  T rv = ...;

  if (rv != null)
  {

然后在编译时使用通常的重载决策(包括!=的一些特殊规则)修复要使用的==的重载。 rv的类型T已知为SomeBaseClass的引用类型eqaul或派生自operator !=(object, object)。因此,基于此选择最佳过载。如果SomeBaseClass未定义(或&#34;继承&#34;)适当的重载,则可能是T重载(内置)。

在运行时,即使SomeEqualityOverloadingClass的实际替换恰好是更具体的类型virtual(当然满足约束),这并不意味着新的重载解析将在运行时发生!

这与.Equals(object)方法dynamic不同。

在C#中,泛型不像模板那样工作,它们与dynamic不同。

如果你真的想要if ((dynamic)rv != null)重载解析(绑定在运行时而不是在编译时),可以说IMAPFolder