我有一个将Action作为参数的方法。操作存储在队列中,并在特定资源可用时执行。在我调用Action之前,我想检查它是否是其成员的实例是否为null。
我用以下愚蠢的例子做了一个简单的测试。在将invokee设置为null之后,Action成功调用,并且正如预期的那样,在尝试访问null invokee上的属性时,我得到了NullReferenceException。在运行时检查Action时,没有任何内容突然出现,表明我可以确定它的实例是否为null。
我想我可以将Action和实例作为参数传递,并在调用之前测试实例是否为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();
}
}
答案 0 :(得分:3)
使用Target
属性检查:
if(yourAction.Target != null) {
//...
}
任何Delegate
类型都有一个名为Target
的属性,因此您也可以将其用于其他类型的委托。
更新:事实上,当您使用Action
来包装某个对象的某个方法时,该对象将永远不会被处理掉,这意味着NullReferenceException
可以在这种情况下抛出除非你包装另一个方法的另一个方法,这个方法与null对象有关。
答案 1 :(得分:3)
这不起作用。
Action
并不关心你从中得到它的原始引用变量,它会复制引用值,从而有自己的参考。
请注意,这也意味着只要您仍然具有对委托的引用,即使您没有对原始对象的其他引用,它仍然不符合垃圾回收的条件。
.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
答案 2 :(得分:0)
好吧,不要试图与Lasse的相当详细的回应争论,我想在这上面花5美分。
当您调用bazooka类的LockAndLoad方法时,只需将传递给方法的方法添加到bazooka类的List explosionions集合中。将您传递给LockAndLoad方法的方法(在您的示例中为“rocketLauncher”)中的类的实例化取消对该集合没有影响,这意味着特定Action的Target属性不会变为null。您必须在清空类的实例化之前从该集合中显式删除该方法。
bazooka.Unload(rocketLauncher.BlowStuffUp);
rocketLauncher = null;
当然,只有在你的火箭筒类中将你的火箭筒类修改为以下方法时才有效:
public void Unload(Action unloadIt)
{
if (explosions.Contains(unloadIt))
explosions.Remove(unloadIt);
}
这可能不是你所希望的,但我希望它无论如何都会有所帮助。