我有一个方法,它将Action作为参数。操作存储在队列中,并在特定资源可用时执行。在调用Action之前,我想检查它所属的实例是否为null。
我用下面这个愚蠢的例子做了一个简单的测试。Action在将invokee设置为null后成功调用,正如预期的那样,我在尝试访问null invokee上的属性时得到了NullReferenceException。当我在运行时检查Action时,没有任何东西表明我可以确定它的实例是否为null。
我想我可以传入Action和实例作为参数,并在调用之前测试实例是否为null。是否可以测试null invokee,或者这只是我设计不好的一个例子?
更新:
我加了一行
if(explosion.Target!=null)
到Bazooka.Fire(),以检查空目标,但在我的示例中它仍在调用委托。
public void LetsDoThis()
{
var bazooka = new Bazooka();
var rocketLauncher = new RocketLauncher();
bazooka.LockAndLoad(rocketLauncher.BlowStuffUp);
rocketLauncher = null;
bazooka.Fire();
bool wasThisCompletelyAwesome = rocketLauncher.ThisIsAwesome;
}
public class RocketLauncher
{
public void BlowStuffUp()
{
bool stuffIsBlowingUp = true;
}
public bool ThisIsAwesome
{
get
{
return true;
}
}
}
public class Bazooka
{
private List<Action> explosions = new List<Action>();
public void LockAndLoad(Action loadIt)
{
this.explosions.Add(loadIt);
}
public void Fire()
{
foreach (Action explosion in explosions)
if (explosion.Target != null)
explosion.Invoke();
}
}
这行不通。
Action
在任何方面都不关心您从中获得的原始引用变量,它会复制引用value的副本,因此具有自己的引用。
请注意,这也意味着,只要您仍然有对委托的引用,即使您没有对原始对象的其他引用,它仍然不符合垃圾回收的条件。
.Target
属性指的是应该在其上调用委托引用的方法的实例,基本上是该方法的this
"参数"。
因此,要获得null
目标,您需要从静态方法获得委托,请在LINQPad:中尝试
void Main()
{
Action a = Static.StaticMethod;
(a.Target == null).Dump();
}
public static class Static
{
public static void StaticMethod() { }
}
您可以看到,委托使用以下LINQPad代码携带自己的实例:
void Main()
{
Dummy d = new Dummy { Name = "A" };
Action a = d.Method;
d = new Dummy { Name = "B" };
Action b = d.Method;
d = null;
a();
b();
}
public class Dummy
{
public string Name { get; set; }
public void Method()
{
Debug.WriteLine("Name=" + Name);
}
}
这里的输出将是
Name=A
Name=B
根据要求,让我澄清实例、引用和变量之间的区别。
当您构造一个对象实例时,如下所示:
var rocketLauncher = new RocketLauncher();
您正在做的是调用一个称为构造函数的方法。此构造函数的返回值是对新构造的对象的引用。基本上,它是一个指针,意味着对象现在在内存中的位置的内存地址。如果它让你更容易理解这个答案的其余部分,你可以认为它只是一个数字。
此外,您还声明了一个变量rocketLauncher
来保存这个引用,这个数字。
请注意,对象与变量是分开的,它们是两个不同的项。在内存中的一个地方有一个对象,在另一个地方,有一个变量,包含对该对象的引用,它的地址,那个数字。
所以当你这样做的时候:
bazooka.LockAndLoad(rocketLauncher.BlowStuffUp);
让我们稍微简化一下:
Action a = rocketLauncher.BlowStuffUp;
// bazooka.LockAndLoad(a);
让我们忘记调用LockAndLoad方法的部分,看看当我们将方法BlowStuffUp
"转换"为Action
类型的委托时发生了什么。
基本上,有两件事被"抓住"了:
- 让委托参考哪种方法
- 要调用该方法的对象实例
你可以将其比作以下代码:
MethodReference = rocketLauncher.BlowStuffUp;
object target = rocketLauncher;
// wrap this into a delegate
这意味着您现在有两个对该对象的引用,一个位于rocketLauncher
变量中,另一个现在位于委托中。
您对变量所做的操作不会以任何方式更改该委托的值,它仍然指向与以前相同的对象。基本上,它复制了那个数字。该数字仍然位于代理内部。
这几乎与此完全相同:
int a = 10;
int b = a;
a = 0;
// b is still 10
因此,总而言之,委托的.Target
属性无论如何都不知道或关心您从中获得委托的原始变量。将原始变量中的引用值复制到委托中,之后对该变量所做的操作没有任何区别。
所以基本上:
- 实例是对象,它位于内存中的某个位置
- 引用基本上就是它的地址,你可以把它看作一个数字
- 变量是一个可以存储引用的地方
现在,如果真的想让委托依赖于变量,并在调用它时关心它现在的值,该怎么办?
好吧,有一种方法可以做到:
bazooka.LockAndLoad(delegate
{
if (rocketLauncher != null)
rocketLauncher.BlowStuffUp();
});
这将生成一个匿名方法,该方法将捕获变量本身,然后在该匿名方法中,您可以在调用委托时显式检查变量的值。如果关于匿名方法的这一部分没有意义,你应该在这里问另一个问题(最好是在阅读了一些关于匿名方法、捕获的变量以及SO上现有的一些问题之后)。
要测试匿名方法,请在LINQPad中测试以下代码:
void Main()
{
object dummy = new object();
Action a = delegate
{
if (dummy != null)
Debug.WriteLine("not null");
else
Debug.WriteLine("null");
};
a();
dummy = null;
a();
}
它将打印出来:
not null
null
使用Target
属性检查:
if(yourAction.Target != null) {
//...
}
任何Delegate
类型都有一个名为Target
的属性,因此您也可以将其用于其他类型的委托。
更新:事实上,当您使用Action
包装对象的某个方法时,该对象永远不会被丢弃,这意味着在这种情况下不能抛出NullReferenceException
,除非包装另一个对象的另一个方法,并且该方法与null对象有关。
好吧,我不想以任何方式与Lasse相当详细的回应争论,我想在这方面投入我的5美分。
当您调用bazooka类的LockAndLoad方法时,只需将传递给该方法的方法添加到bazoka类的List爆炸集合中。将其方法传递给LockAndLoad方法(在您的示例"rocketLauncher"中)的类的实例化为null对该集合没有影响,这意味着特定Action的Target属性不会变为null。在取消类的实例化之前,您必须从该集合中显式删除该方法。
bazooka.Unload(rocketLauncher.BlowStuffUp);
rocketLauncher = null;
当然,只有当你在火箭筒类中将火箭筒类修改为以下方法时,这才有效:
public void Unload(Action unloadIt)
{
if (explosions.Contains(unloadIt))
explosions.Remove(unloadIt);
}
这可能不是你所希望的,但我希望它无论如何都有帮助。