C#使代理引用无效

时间:2016-10-12 14:41:02

标签: c# delegates

让我们说我是一个管理一堆代表的班级。出于某种原因,在某些时候我需要使堆栈上的随机委托无效,所以,因为我传递一个委托的变量作为引用,我希望设置为null该变量,将阻止堆栈调用该方法只测试是否它是null,但相反,对方法的引用仍然存在,因此将被调用。这里有一些愚蠢的代码来表明我的意思:

public delegate void DlgtVoidVoid();

public class TestPile {
    Pile pileStack = new Pile();
    DlgtVoidVoid _delvar1 = null;
    DlgtVoidVoid _delvar2 = null;
    public void Main()
    {
        _delvar1 = voidmethod1;
        _delvar2 = voidmethod2;
        pileStack.PushItem(ref _delvar1);
        pileStack.PushItem(ref _delvar2);
        //trying to invalidate the first delegate handler
        _delvar1 = null;
        pileStack.InvokeAndPop();//invoke 2
        pileStack.InvokeAndPop();//shouldn't voidmethod1 call fail?
        //but instead also voidmethod1 will be called: why?
    }
    void voidmethod1()
    {
        Console.WriteLine("calling void method one");
    }
    void voidmethod2()
    {
        Console.WriteLine("calling void method two");
    }
}

这只是一个小堆栈管理器,只是一个例子:

public class Pile {
    DlgtVoidVoid[] thepile = new DlgtVoidVoid[10];
    int pileindex = -1;
    public void PushItem(ref DlgtVoidVoid item)
    {
        thepile[++pileindex] = item;
    }
    public DlgtVoidVoid PeekItem()
    {
        return thepile[pileindex];
    }
    public DlgtVoidVoid PopItem()
    {
        return thepile[pileindex--];
    }
    public void InvokeAndPop()
    {
        if(pileindex >= 0) {
            if(PeekItem() != null)
                PopItem().Invoke();
        }
    }
}

很明显,在c#中传递一个参考变量并不像我预期的那样工作,比方说,在C ++或其中一些,因此有没有办法在C#中实现它必须改变代码逻辑吗?

2 个答案:

答案 0 :(得分:1)

我发现你的代码设计得不是很好......将局部变量设置为NULL不会让任何人期望其他类内部数据发生任何变化。即使你能做到这一点,它肯定会迟早会导致错误。

最好在List<DlgtVoidVoid>类中使用Pile代替数组。而不是使委托引用为空,您最好创建一个特殊方法“Remove”,随后将调用List的相应方法。

public class Pile {
    private List<DlgtVoidVoid> thepile = new List<DlgtVoidVoid>();

    public void PushItem(DlgtVoidVoid item)
    {
        thepile.Add(item);
    }
    public DlgtVoidVoid PeekItem()
    {
        return thepile[thepile.Count-1];
    }
    public DlgtVoidVoid PopItem()
    {
        var item = thepile[thepile.Count-1];
        thepile.RemoveAt(thepile.Count-1);
        return item;
    }
    public void InvokeAndPop()
    {
        PopItem()();
    }

    public void Remove(DlgtVoidVoid deletageToRemove) {
        thepile.Remove(deletageToRemove);
    }
}

//...

public void Main()
{
    _delvar1 = voidmethod1;
    _delvar2 = voidmethod2;
    pileStack.PushItem(ref _delvar1);
    pileStack.PushItem(ref _delvar2);
    //trying to invalidate the first delegate handler
    pileStack.Remove(_delvar1);

    pileStack.InvokeAndPop();//invoke 2
    pileStack.InvokeAndPop();//Now it will throw you IndexOutOfRange exception. You could handle it better inside of the Pile methods
}

答案 1 :(得分:0)

通过引用传递与C ++几乎相同,这意味着您可以修改调用者中的变量值。但就像在C ++中一样,除非你存储了一个指向指针的指针,否则你仍然无法在以后更改该值。

在您的代码中,您存储的是对象,而不是对变量的引用,因此您必须调用remove方法来实际从数组中删除值。编辑变量不会对该对象的任何其他引用执行任何操作。

您当然可以拥有一个包含此委托的对象。然后,如果您修改该外部对象,则堆栈中的内部委托也将变为null,但它会添加另一个间接层,并且比调用remove方法更加混乱。