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

时间:2013-11-23 14:09:14

标签: c# .net delegates

我有一个将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();
        }
    }

3 个答案:

答案 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);
}

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