如何覆盖 NullReferenceException 行为以获得更好的信息量更大的错误?
How to override NullReferenceException behavior for a better more informative error?
上下文
在 Unity 中,NullReferenceException 仅记录错误,不会将 IDE 踢入调试模式。游戏有时很难为错误 100% 发生的情况重新创建相同的环境。
组件变量通过unity编辑器序列化暴露,设计者经常忘记赋值,造成组件成员变量为NULL的这种情况
由于 Unity 的特殊情况,NullReferenceException 是最常见的异常,我正在尝试探索使异常立即更加明显的方法。
问题
NullReferenceException 消息非常无用。对我来说,当我看到 NullReferenceException 时,我什至不知道哪个变量是 NULL。
player.GetComponent<Movement>().AnothingVariable.CallFunction() // NullReferenceException, which part caused it?
当前错误信息:
NullReferenceException: Object reference not set to an instance of an object
理想的错误信息:
NullReferenceException: Trying to access CallFunction in type Movement, in GameObject "Player" from component "Controller".
据我所知,您无法更改此设置。
如果您正在访问一个对象的 属性 并且它有可能为空,您应该检查它是否为空。您可以使用更现代版本的 C#
的新空检查功能
A?.B?.C?.D
如果 A 或 B 或 C 为空,则表达式 returns 为空,您不会得到异常。但是,您不会获得有关空值的信息。据我所知,唯一的方法是明确检查参数并抛出您自己的异常(如果这是您想要发生的)。
if (a == null)
{
throw new ArgumentNullException("a");
}
B 和 C 等
如果异常没有帮助,这通常意味着一行中的命令太多。一个简单的解决方案是将代码拆分成更多行,使用临时变量来存储每个成员调用(函数、属性 或字段)的结果。
这可能不会 是性能损失。编译器和 JiT 编译器应该能够注意到这些变量大部分是 "useless" 并在发布构建期间将它们删除。您将获得更好的可读性和可调试代码,几乎没有惩罚。
与其试图找出什么是 null
,更好的策略是针对这种情况进行防御性编程。通常使用的方法是使用 guard clauses 来确保变量永远不可能是 null
.
实例化
可以将保护子句添加到对象的构造函数中,以确保字段永远不会包含 null
。
public class SomeClass
{
// Marking fields readonly ensures they cannot be set
// (thus cannot be set to null) from anywhere outside
// of the constructor
public readonly string field1;
public readonly Type field2;
public SomeClass(string field1, Type field2)
{
// Throwing ArgumentNullException ensures the class
// can never be created with missing (null) values.
if (field1 == null)
throw new ArgumentNullException(nameof(field1));
if (field2 == null)
throw new ArgumentNullException(nameof(field2));
this.field1 = field1;
this.field2 = field2;
}
public string Field1
{
get { return field1; } // Never null
}
public Type Field2
{
get { return field2; } // Never null
}
}
方法调用
保护子句也可用于确保方法参数不为空,因此您不会以讨厌的 NullRefereneceExceptions 结束。
public void DoSomething(string param1, Type param2)
{
if (param1 == null)
throw new ArgumentNullException(nameof(param1));
if (param2 == null)
throw new ArgumentNullException(nameof(param2));
// param1 and param2 can now be used safely because they cannot be null
}
使用保护子句并不总是实用,但你应该在任何地方都使用null
是没有用的到程序中,以防止必须在任何地方添加 null
检查逻辑。
在 null
对程序有意义的情况下(正如 Dave 也提到的),您将需要明确检查以防止它。
if (variable != null && variable.Field != null)
{
// Do something with variable.Field
var foo = variable.Field;
}
或
var foo = variable?.Field;
空对象模式
最后,如果程序需要引用 null
比显式检查任何地方更好的方法是创建一个具体对象 表示 null
而不是实际使用 null
对象指针。这被称为 Null Object Pattern.
public interface IService
{
void DoSomething();
}
public class NullService : IService
{
public void DoSomething()
{
// Do nothing at all to represent a no-op
}
}
public class MyService : IService
{
public void DoSomething()
{
// Do something interesting
Console.WriteLine("Something was done.");
}
}
那么,在程序中,可以有一个MyService
实例来表示一个实数值,或者一个NullService
来表示null
.
IService service1 = new MyService();
IService service2 = new NullService();
service1.DoSomething();
service2.DoSomething(); // Doesn't throw NullReferenceException
上下文
在 Unity 中,NullReferenceException 仅记录错误,不会将 IDE 踢入调试模式。游戏有时很难为错误 100% 发生的情况重新创建相同的环境。
组件变量通过unity编辑器序列化暴露,设计者经常忘记赋值,造成组件成员变量为NULL的这种情况
由于 Unity 的特殊情况,NullReferenceException 是最常见的异常,我正在尝试探索使异常立即更加明显的方法。
问题
NullReferenceException 消息非常无用。对我来说,当我看到 NullReferenceException 时,我什至不知道哪个变量是 NULL。
player.GetComponent<Movement>().AnothingVariable.CallFunction() // NullReferenceException, which part caused it?
当前错误信息:
NullReferenceException: Object reference not set to an instance of an object
理想的错误信息:
NullReferenceException: Trying to access CallFunction in type Movement, in GameObject "Player" from component "Controller".
据我所知,您无法更改此设置。 如果您正在访问一个对象的 属性 并且它有可能为空,您应该检查它是否为空。您可以使用更现代版本的 C#
的新空检查功能A?.B?.C?.D
如果 A 或 B 或 C 为空,则表达式 returns 为空,您不会得到异常。但是,您不会获得有关空值的信息。据我所知,唯一的方法是明确检查参数并抛出您自己的异常(如果这是您想要发生的)。
if (a == null)
{
throw new ArgumentNullException("a");
}
B 和 C 等
如果异常没有帮助,这通常意味着一行中的命令太多。一个简单的解决方案是将代码拆分成更多行,使用临时变量来存储每个成员调用(函数、属性 或字段)的结果。
这可能不会 是性能损失。编译器和 JiT 编译器应该能够注意到这些变量大部分是 "useless" 并在发布构建期间将它们删除。您将获得更好的可读性和可调试代码,几乎没有惩罚。
与其试图找出什么是 null
,更好的策略是针对这种情况进行防御性编程。通常使用的方法是使用 guard clauses 来确保变量永远不可能是 null
.
实例化
可以将保护子句添加到对象的构造函数中,以确保字段永远不会包含 null
。
public class SomeClass
{
// Marking fields readonly ensures they cannot be set
// (thus cannot be set to null) from anywhere outside
// of the constructor
public readonly string field1;
public readonly Type field2;
public SomeClass(string field1, Type field2)
{
// Throwing ArgumentNullException ensures the class
// can never be created with missing (null) values.
if (field1 == null)
throw new ArgumentNullException(nameof(field1));
if (field2 == null)
throw new ArgumentNullException(nameof(field2));
this.field1 = field1;
this.field2 = field2;
}
public string Field1
{
get { return field1; } // Never null
}
public Type Field2
{
get { return field2; } // Never null
}
}
方法调用
保护子句也可用于确保方法参数不为空,因此您不会以讨厌的 NullRefereneceExceptions 结束。
public void DoSomething(string param1, Type param2)
{
if (param1 == null)
throw new ArgumentNullException(nameof(param1));
if (param2 == null)
throw new ArgumentNullException(nameof(param2));
// param1 and param2 can now be used safely because they cannot be null
}
使用保护子句并不总是实用,但你应该在任何地方都使用null
是没有用的到程序中,以防止必须在任何地方添加 null
检查逻辑。
在 null
对程序有意义的情况下(正如 Dave 也提到的),您将需要明确检查以防止它。
if (variable != null && variable.Field != null)
{
// Do something with variable.Field
var foo = variable.Field;
}
或
var foo = variable?.Field;
空对象模式
最后,如果程序需要引用 null
比显式检查任何地方更好的方法是创建一个具体对象 表示 null
而不是实际使用 null
对象指针。这被称为 Null Object Pattern.
public interface IService
{
void DoSomething();
}
public class NullService : IService
{
public void DoSomething()
{
// Do nothing at all to represent a no-op
}
}
public class MyService : IService
{
public void DoSomething()
{
// Do something interesting
Console.WriteLine("Something was done.");
}
}
那么,在程序中,可以有一个MyService
实例来表示一个实数值,或者一个NullService
来表示null
.
IService service1 = new MyService();
IService service2 = new NullService();
service1.DoSomething();
service2.DoSomething(); // Doesn't throw NullReferenceException