如何识别匿名函数

时间:2010-11-30 19:40:03

标签: c# delegates anonymous-function

我有一个创建List<Action<int>>的类,并将其保留到以后的时间。此类可以从此列表中添加和删除委托。只要人们不太喜欢,这种方法就能很好地运作。为了打击匿名函数(无法删除),我检查委托的目标为null。如果它为null我抛出异常。当存在包含函数的匿名委托时,会出现此问题。这有一个目标,但同样是不可移除的。下面的简化代码说明了我的问题

 public class MyDelegateContainer
 {
    List<Action<int>> m_Container = new List<Action<int>>();

    public void Add(Action<int> del)
    {
        if (del.Target == null) 
        { 
            throw new Exception("No static handlers"); 
        }
        m_Container.Add(del);
    }

    public bool Remove(Action<int> del)
    {
        if (m_Container.Contains(del))
        {
            m_Container.Remove(del);
            return true;
        }

        return false;
    }
}

public class MyFakeActionClass
{
    public void Test(int temp) { }
}

class Program
{
    static void Main(string[] args)
    {
        bool removed = false;
        int counter = 0;
        MyDelegateContainer container = new MyDelegateContainer();
        MyFakeActionClass fake = new MyFakeActionClass();
        //container.Add(p => { }); //Throws, this is what I want to happen
        container.Add(fake.Test); //Works, this is the use case
        removed = container.Remove(fake.Test); //Works, this is the use case
        Debug.Assert(removed);
        container.Add(p => { fake.Test(p); counter++; }); //Works but I would like it not to
        removed = container.Remove(p => { fake.Test(p); counter++; }); //doesn't work
        Debug.Assert(removed);
    }
}

我需要一些方法来识别

   p => { fake.Test(p); counter++; }

是一个匿名函数,所以我可以抛出,如果有人尝试它。谢谢你的帮助

编辑:我应该注意,我可以使用Action<int>变量来表示匿名函数,但一切都可行,但添加和删除在实践中从不在同一范围内。

6 个答案:

答案 0 :(得分:4)

在您的示例中,调用者负责删除处理程序。因此,如果调用者不想删除处理程序,则无论处理程序是否是匿名委托/ lambda,它都不会被删除。

我的建议是将委托容器更改为:

public class MyDelegateContainer
{
    List<Action<int>> m_Container = new List<Action<int>>();

    public Action Add(Action<int> del)
    {
        m_Container.Add(del);

        return new Action(() =>
        {
            m_Container.Remove(del);
        });
    }
}

调用者仍然负责删除处理程序,但它不是再次将处理程序传递给容器,而是接收一个“令牌”,它可以保存并稍后用于删除处理程序。

答案 1 :(得分:3)

无法可靠地确定函数是否为“匿名”,因为所有函数都具有CLR的名称。它只是在生成它的语言中匿名,而且依赖于编译器。您可以确定Microsoft当前C#编译器使用的算法,只是让它停止在C#5或Mono上工作。

由于您希望阻止您的类型的用户编写错误使用它的代码,您只需要在某些点处抛出异常,这将导致程序崩溃。我找到的是在找不到目标委托时在Remove函数中抛出异常。此时,您的用户仍然会遇到崩溃,解决问题的唯一方法是以某种可移动的方式编写委托。

作为一个额外的好处,你会发现有人试图删除代表两次或者从未添加过代表的错误。代码如下所示:

public bool Remove(Action<int> del) 
{ 
    if (m_Container.Contains(del)) 
    { 
        m_Container.Remove(del); 
        return true; 
    } 

    throw new ArgumentException("Attempt to remove nonexistent delegate");
} 

答案 2 :(得分:1)

我会使用内省来检查方法的名称。

匿名方法通常具有非常可预测的名称。 (我不记得确切的格式,但是运行一些测试,这应该是显而易见的。)

缺点是如果有人创建了一个非匿名方法,但决定将其命名为anonMethod123(或者格式为......),它将被错误地拒绝。

答案 3 :(得分:0)

当然你可以删除一个匿名方法,你只需要引用相同的匿名方法。

var myAnonymousMethod = p => { fake.Test(p); counter++; };
container.Add(myAnonymousMethod);
removed = container.Remove(myAnonymousMethod);

答案 4 :(得分:0)

正如jonnii在评论中所建议的,另一种可以实现它的方法是使用字典:

public class MyDelegateContainer 
{ 
    Dictionary<string, Action<int>> m_Container =
        new Dictionary<string, Action<int>>(); 

    public void Add(string key, Action<int> del) 
    { 
        m_Container.Add(key, del);
    } 

    public bool Remove(string key) 
    { 
        return m_Container.Remove(key); 
    } 
}

然后,只需知道用于添加代码的名称,就可以轻松地删除代码中某个任意点的已知委托:

    container.Add("fake.Test", fake.Test);
    removed = container.Remove("fake.Test");
    Debug.Assert(removed);   
    container.Add("anon", p => { fake.Test(p); counter++; });
    removed = container.Remove("anon"); // works!
    Debug.Assert(removed);   

答案 5 :(得分:0)

老问题我知道,但我认为这将是一种检查方法是否是匿名的当前(以及未来)校对的方法:

bool isAnonymous = !System.CodeDom.Compiler.CodeGenerator.IsValidLanguageIndependentIdentifier(del.Method.Name);

如果在编译时使用匿名方法的运行时名称,则该名称必须无效,以确保它不会发生冲突。