调用Action-确定它所属的实例是否为null



我有一个方法,它将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);
}

这可能不是你所希望的,但我希望它无论如何都有帮助。

最新更新