我有一个创建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>
变量来表示匿名函数,但一切都可行,但添加和删除在实践中从不在同一范围内。
答案 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);
如果在编译时使用匿名方法的运行时名称,则该名称必须无效,以确保它不会发生冲突。