Как определить анонимную функцию - PullRequest
4 голосов
/ 30 ноября 2010

У меня есть класс, который создает List<Action<int>> и удерживает их до более позднего времени. Этот класс может добавлять и удалять делегатов из этого списка. Это работает хорошо, пока люди не слишком увлекаются. Для борьбы с анонимной функцией (которую нельзя удалить) я проверяю, чтобы цель делегата была нулевой. Если его ноль, я бросаю исключение. Проблема возникает, когда есть анонимный делегат, который содержит функцию. Это имеет цель, но так же неустранимо. Упрощенный код ниже иллюстрирует мои проблемы

 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> для анонимной функции, и все бы работало, но на практике Add и Remove никогда не бывают в одной области.

Ответы [ 6 ]

4 голосов
/ 30 ноября 2010

В вашем примере вызывающая сторона отвечает за удаление обработчика.Таким образом, если вызывающая сторона не хочет удалять обработчик, он не будет удален, независимо от того, является ли обработчик анонимным делегатом / лямбда или нет.

Я предлагаю изменить контейнер делегата начто-то вроде этого:

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

Вызывающий по-прежнему отвечает за удаление обработчика, но вместо повторной передачи обработчика в контейнер он получает «токен», который он может сохранить и использовать позже для удаленияобработчик.

3 голосов
/ 30 ноября 2010

Невозможно надежно определить, является ли функция «анонимной», поскольку все функции имеют имена для CLR.Он генерируется анонимно только в языке, который зависит от компилятора.Вы можете определить алгоритм, используемый текущим компилятором C # от Microsoft, только чтобы он перестал работать на 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");
} 
1 голос
/ 30 ноября 2010

Я бы использовал самоанализ для проверки имен методов.

Анонимные методы обычно имеют очень предсказуемые имена. (Я не помню точный формат, но проведу несколько тестов, и это должно быть очевидно).

Недостатком было бы то, что если кто-то создал неанонимный метод, но решил назвать его anonMethod123 (или каков бы ни был формат ...), он был бы ложно отклонен.

0 голосов
/ 24 апреля 2014

Старый вопрос, который я знаю, но я думаю, что это будет текущий (и будущий) проверенный способ проверки, является ли метод анонимным:

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

Имя среды выполнения анонимного метода должно быть недействительным, если оно используется во время компиляции, чтобы гарантировать, что оно не конфликтует.

0 голосов
/ 30 ноября 2010

Как предложил Джонни в комментарии, другой способ реализовать его - это словарь:

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);   
0 голосов
/ 30 ноября 2010

Конечно, вы можете удалить анонимный метод, вам просто нужна ссылка на тот же анонимный метод.

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