题
我想有一个能够处理自身空引用的类.我怎样才能做到这一点?扩展方法是我能想到的唯一方法,但我想如果有一些我不了解的关于C#的漂亮的东西我会问.
例
我有一个叫做User属性调用的类IsAuthorized.
什么时候User正确实例化IsAuthorized有一个实现.但是,当我的User引用包含null时,我希望调用IsAuthorized返回false而不是爆炸.
解
很多很好的答案.我最终使用其中三个来解决我的问题.
不幸的是,我只能选择其中一个作为我接受的答案,所以如果你正在访问这个页面,你应该花时间投票所有这三个以及给出的任何其他优秀答案.
Zai*_*sud 29
如何正确的面向对象解决方案?这正是Null对象设计模式的用途.
您可以提取IUser接口,让User对象实现此接口,然后创建NullUser对象(也实现IUser)并始终在IsAuthorized属性上返回false.
现在,修改使用代码以依赖IUser而不是User.客户端代码将不再需要空检查.
代码示例如下:
public interface IUser
{
// ... other required User method/property signatures
bool IsAuthorized { get; }
}
public class User : IUser
{
// other method/property implementations
public bool IsAuthorized
{
get { // implementation logic here }
}
}
public class NullUser : IUser
{
public bool IsAuthorized
{
get { return false; }
}
}
Run Code Online (Sandbox Code Playgroud)
现在,您的代码将返回IUser而不是User,客户端代码将仅依赖于IUser:
public IUser GetUser()
{
if (condition)
{
return new NullUser(); // never return null anymore, replace with NullUser instead
}
return new User(...);
}
Run Code Online (Sandbox Code Playgroud)
Ree*_*sey 18
但是,当我的User引用包含null时,我希望IsAuthorized始终返回false而不是expand.
只有在IsAuthorized静态方法的情况下才能执行此操作,在这种情况下,您可以检查null.这就是扩展方法可以做到这一点的原因 - 它们实际上只是调用静态方法的不同语法.
调用方法或属性(例如IsAuthorized实例方法)需要实例.只是调用实例方法(包括属性getter)的操作null将触发异常.您的类不会引发异常,但是当您尝试使用(null)引用时,运行时本身不会引发异常.在C#中没有办法解决这个问题.
JSB*_*ach 11
如果变量为null,则意味着它不引用任何对象,因此在类方法中处理null引用是没有意义的(我认为这在技术上是不可能的).
您应该通过在调用"IsAuthorized"或之前的事件之前进行检查来保证它不为空.
编辑:找到一个解决方法来执行此操作将是一件坏事:某人理解行为会让人感到困惑,因为它不是编程语言的"预期"行为.它还可能导致您的代码隐藏一些问题(一个空值,它应该是一个对象)并创建一个很难找到的错误.这就是说:这肯定是一个坏主意.
问题不在于创建这样的方法.这是调用方法.如果您if(this == null)在代码中进行测试,则完全有效.我想它可以被编译器根据它"不可能"被击中而被优化掉,但幸运的是它不是.
但是,当你调用方法时,它将通过它来完成callvirt,因此它不是直接调用方法,而是找到为特定实例调用的方法版本,就像使用虚方法一样.因为对于空引用会失败,所以你的完美自我测试方法在调用之前就会失败.
C#故意这样做.根据Eric Gunnerson的说法,这是因为他们认为让你这样做会有点奇怪.
我一直都不明白为什么让一个以C++为模型的.NET语言在.NET和同一家公司生产的C++编译器中做了完全允许的事情,*被认为有点奇怪.我一直认为它不被允许有点奇怪.
您可以添加来自调用该类的另一种语言(F#或IL)的内容,或者用于Reflection.Emit生成执行此操作的委托,并且该方法可以正常工作.例如,以下代码将调用GetHashCode定义的版本object(即,即使GetHashCode被覆盖,也不调用覆盖),这是一个可以安全地调用null实例的方法的示例:
DynamicMethod dynM = new DynamicMethod(string.Empty, typeof(int), new Type[]{typeof(object)}, typeof(object));
ILGenerator ilGen = dynM.GetILGenerator(7);
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Call, typeof(object).GetMethod("GetHashCode"));
ilGen.Emit(OpCodes.Ret);
Func<object, int> RootHashCode = (Func<object, int>)dynM.CreateDelegate(typeof(Func<object, int>));
Console.WriteLine(RootHashCode(null));
Run Code Online (Sandbox Code Playgroud)
关于这个的一个好处是,你可以坚持下去,RootHashCode所以你只需要构建它一次(比如在静态构造函数中),然后你可以重复使用它.
这当然是让其他代码通过空引用调用您的方法没有价值,因为您建议的扩展方法是您唯一的选择.
当然还值得注意的是,如果你用一种没有C#怪癖的语言编写,你应该提供一些替代方法来获取"默认"结果以调用空引用,因为C#人可以'得到它.就像C#一样,人们应该避免使用公共名称之间仅存在案例的差异,因为某些语言无法解决这个问题.
编辑:你的问题IsAuthorized被调用的一个完整的例子,因为投票表明有些人不相信它可以完成(!)
using System;
using System.Reflection.Emit;
using System.Security;
/*We need to either have User allow partially-trusted
callers, or we need to have Program be fully-trusted.
The former is the quicker to do, though the latter is
more likely to be what one would want for real*/
[assembly:AllowPartiallyTrustedCallers]
namespace AllowCallsOnNull
{
public class User
{
public bool IsAuthorized
{
get
{
//Perverse because someone writing in C# should be expected to be friendly to
//C#! This though doesn't apply to someone writing in another language who may
//not know C# has difficulties calling this.
//Still, don't do this:
if(this == null)
{
Console.Error.WriteLine("I don't exist!");
return false;
}
/*Real code to work out if the user is authorised
would go here. We're just going to return true
to demonstrate the point*/
Console.Error.WriteLine("I'm a real boy! I mean, user!");
return true;
}
}
}
class Program
{
public static void Main(string[] args)
{
//Set-up the helper that calls IsAuthorized on a
//User, that may be null.
DynamicMethod dynM = new DynamicMethod(string.Empty, typeof(bool), new Type[]{typeof(User)}, typeof(object));
ILGenerator ilGen = dynM.GetILGenerator(7);
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Call, typeof(User).GetProperty("IsAuthorized").GetGetMethod());
ilGen.Emit(OpCodes.Ret);
Func<User, bool> CheckAuthorized = (Func<User, bool>)dynM.CreateDelegate(typeof(Func<User, bool>));
//Now call it, first on null, then on an object
Console.WriteLine(CheckAuthorized(null)); //false
Console.WriteLine(CheckAuthorized(new User()));//true
//Wait for input so the user will actually see this.
Console.ReadKey(true);
}
}
}
Run Code Online (Sandbox Code Playgroud)
哦,这是一个真实的实际问题.关于C#行为的好处是它会导致对null引用的调用失败,因为它们无论如何都会失败,因为它们访问中间的某个字段或虚拟区域.这意味着我们不必担心在编写调用时我们是否处于null实例中.但是,如果您希望在完全公共方法(即公共类的公共方法)中防弹,那么您就不能依赖于此.如果方法的第1步始终跟随第2步至关重要,并且第2步只有在空实例上调用时才会失败,那么应该进行自动空检查.这很少会发生,但它可能会导致非C#用户的错误,如果不使用上述技术,您将永远无法在C#中重现.
*虽然,这是特定于他们的编译器 - 它是根据C++标准IIRC未定义的.
| 归档时间: |
|
| 查看次数: |
1577 次 |
| 最近记录: |