一个值为空,但检查返回了其他结果?

4

编辑:这篇文章解释了一切! - Unity在销毁UnityEngine.Objects时会创建托管的虚拟包装器。这意味着,如果你销毁一个UEObject,C#包装器仍可能不为null。==运算符以自定义方式实现,因此当你销毁UEObject时,检查== null将返回true。这显然与泛型不兼容。


这真的让我发疯了。我有这个方法在这里:
public static void AssertNotNullAfterAssignment<T>(ref T value, Func<T> get, string msg) where T : class
{
    if (value == null)
        value = get();
    if (value == null)
        throw new NullReferenceException(msg);
}

我在开始时调用了一个空引用:
AssertNotNullAfterAssignment(ref fovMeshFilter, GetComponent<MeshFilter>, "fovMeshFilter");

“真正疯狂的是,检查语句if (value == null)返回false!尽管value确实为null!”
这里是我制作的一个短视频,展示了这个问题。”
有趣的是,如果我将该方法代码复制/粘贴到我在其中使用它的方法中(在OnEnable中),它就可以工作(基本上与我在视频中在OnEnable中所述的相同)。
所以……如果它在断言方法之外,它就可以工作,但在内部则不行。 我还尝试过使用Equals而不是==,但结果相同。
任何人有任何想法吗?
编辑:这里是OnEnable,我从中调用断言的方法:
private void OnEnable()
{
    //if (fovMeshFilter== null)
    //  fovMeshFilter= GetComponent<MeshFilter>();
    //if (fovMeshFilter== null)
    //  throw new NullReferenceException("fovMeshFilter");

    AssertNotNullAfterAssignment(ref fovMeshFilter, GetComponent<MeshFilter>, "fovMeshFilter");
}

如果我使用未注释的行,它会按预期工作,但是断言方法出于某种只有上帝知道的原因不起作用。
编辑1:我们在这里有什么?

enter image description here

编辑2:

enter image description here

编辑3:

在这个伟大的社区的帮助下,经过几次测试,我找到了解决方案。我发誓这是我尝试的第一件事情!你必须相信我!XD- 只需要使用.Equals而不是== - 就像@Edin在他的答案中所示,对通用对象进行==似乎会调用System.Object== - 但是,调用.Equals应始终解析为Equals的正确重载。我不知道为什么是这样,我的意思是,为什么“==”也不能解析为正确的重载呢?


你的图片回答了你的问题。很明显它不是空的。 - Edin
@vexe,我认为你在调试/鼠标悬停时看到的异常。如果在正常工作期间不弹出异常,请忽略它。但请展开所有小三角。 - nl-x
1
@vexe:这是该对象的DebuggerDisplay字符串。您可以在调试器中设置您的对象显示任何您想要的内容。我猜想对于这个对象,如果某个后备属性为null或其ToString()返回"null",则会写入"null"。无论如何,您的对象不是null,这就是为什么您的检查返回false的原因。 - Edin
是的,我尝试过了。出现了异常。在你的答案下发表了评论。 - vexe
我只是想表达我的哀悼之情,并宣布这激励我今后只使用汇编语言编程,不再使用任何库,以确保安全。 - jackmott
显示剩余29条评论
3个回答

4

你的对象不是null。在调试器中看到null并不意味着它是一个null引用。如果你可以在调试器中展开对象,那么它很有可能不是null引用。也许在你的情况下DebuggerDisplay字符串或ToString()方法返回"null",但这与null引用不同。

以这个类为例

[DebuggerDisplay("null")]
class A { }

一个名为A的实例a
var a = new A();

当您用鼠标将其移到上方时,a|null将在调试器中显示。
您的T肯定具有此属性。覆盖ToString()并返回"null"会在字符串周围添加额外的花括号:a|{null}
编辑:看到您的Edit2让我意识到您真正的问题可能是什么。您的==操作符很可能被重载,因此在某些情况下,当您比较类的实例与null时它会返回true,尽管实例本身不为null。但是,在通用方法中,编译时不知道参数的类型,因此将对您的T参数应用最一般的运算符,即引用比较。这在以下线程中很好地描述了:C# generics class operators not working 这可能是OnEnable()中的比较有效而通用方法内部的比较无效的原因。
然而,这并没有完全证明,因为我看不到你的代码。但您可以验证一下。
以下是运算符与泛型组合不正常工作的完整示例:
class Person
{
  public string Name { get; set; }            

  public static bool operator ==(Person left, Person right)
  {
    // we consider Person null when it either has no Name or it is a null reference.
    if (object.ReferenceEquals(null, left) || left.Name == null)
      return object.ReferenceEquals(null, right);
    return left.Equals(right);
  }

  public static bool operator !=(Person left, Person right) { return !(left == right); }
  // Note that == and != operators should ideally be implemented in combination of Equals() override.
  // This is only for making an example for this question
}

private static bool IsNull<T>(T val)
{
  return val == null;
}

static void Main(string[] args)
{
  Person person = new Person();
  //this will display: person == null => True
  Console.WriteLine("person == null => {0}", person == null);
  //this will display: IsNull<Person>(person)=> False
  Console.WriteLine("IsNull<Person>(person)=> {0}", IsNull(person));
}

谢谢。我明白你的意思。调试器可能会说它是null,但实际上不是。然而,我确信它“是”null。在OnEnable中执行检查返回true。请参见最后一次编辑。 - vexe
我可以确认MeshRendererMeshFilter两者都没有这个调试器属性。在沿着对象的层次结构进行跟进之后,最终到达了UnityEngine.Object,但其ToString实现对反编译器不可见,因此我无法说明MeshFilter/MeshRednererToString是如何实现的。 - vexe
然而,在您的示例中,如果使用Equals,则两个日志的结果都可能为“false”。在这种情况下,使用System.ObjectEqual,因为它在Person中没有重载。我们可以重载它并使其返回person1 == person2。现在,两个日志都打印true。但是,在任何情况下,当使用Equals时,其中一个日志都不会打印出不同的结果 - 在我的情况下,我尝试使用Equals - 仍然存在问题(在OnEnable和断言方法中有不同的结果)。 - vexe
@vexe:我不明白你的观点。在我的例子中,并没有重写Equals()方法,因此调用它将会进行完全不同的比较,与当前的==操作符所做的比较也不同。请看我在代码中对Equals()方法的注释。当你重载相等运算符时,理想情况下应该使用你的“相等逻辑”重写Equals()方法,并在操作符==!=内部调用它。这样,操作符和Equals()方法的行为将是一致的。但出于简单起见,我没有这样做。 - Edin
1
@vexe:Equals是一个被覆盖的实例方法,而运算符是静态(方法)。调用哪个运算符是在编译时决定的。由于编译器不知道你的T将是什么,它使用标准引用比较运算符。然而,要调用哪个Equals的重载是通过你调用它的实例来确定的。知道Equals()起作用意味着它被很好地实现了,而你的问题是由于运算符和泛型使用不兼容造成的。 - Edin
显示剩余5条评论

3
在你的视频中,当你鼠标悬停在value上时,你会发现它是null,但你也会看到一个小三角形在它左边,好像你可以展开它。你试过展开它吗?因为当你将鼠标悬停在null本身上时,你不会看到那个三角形。也许它不是null,而是一个对象,其中.toString()返回null。
编辑
我认为你的对象不是null。你的调试器无法显示正确的值,这就是你截图中出现异常的原因。尝试在你调用的函数内添加var foo = value.mesh;之类的操作,然后对其进行调试。
看起来这个对象不太适合断点调试或监视变量。

+1 表示调试器将其识别为对象,你注意到了表示三角形。 - BenVlodgi
我认为调试器没有出现任何问题,我认为两个属性引发异常的原因可能是它们试图访问底层的空值。 - Ben Aaronson
感谢您的回答@nl-x。我尝试了您的foo方法,但是出现了异常:UnassignedReferenceException: The variable fovMeshFilter of FOV2DEyes has not been assigned. You probably need to assign the fovMeshFilter variable of the FOV2DEyes script in the inspector. - 这基本上说明meshFilter未被分配(值为null)。 - vexe
@vexe 嗯,所以你在 AssertNotNullAfterAssignment() 中执行了 var foo = value.mesh;,然后调试器开始谈论 fovMeshFilter?但是在 AssertNotNullAfterAssignment() 中没有提到 fovMeshFilter - nl-x
谢谢nl-x。问题已解决。请查看最后一次编辑。并查看被接受答案中的链接。 - vexe

0

感谢您的回答。 我正在尝试这个:

 public bool IsDisabled()
{
    Debug.Log("in is disabled check and my image name is " + myImage.sprite.name  +"i return="+ (myImage.sprite == Disabled));
    return (myImage.sprite==Disabled);
       
}

调试日志会返回 false,但返回值却是 true。然后我使用了

return (myImage.sprite.Equals(Disabled));

它运行得很好。真奇怪,因为我没有删除任何游戏对象。


这并没有回答问题。一旦您拥有足够的声望,您就可以在任何帖子下发表评论;相反,提供不需要询问者澄清的答案。- 来自审核 - markonius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接