Сохранение указателя на параметр метода для последующего повторного использования - PullRequest
5 голосов
/ 02 декабря 2011

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

public class Closure<TObject, TVariable>
{
    public TVariable Variable { get; set; }
    public Func<TObject, bool> Predicate { get; set; }
}

Тогда у меня есть класс, который работает с DateTime экземплярами. Один из методов, который должен использовать этот класс замыкания, таков:

// Original Version
public IEnumerable<Item> GetDayData(DateTime day)
{
    this.items.Where(i => i.IsValidForDay(day));
}

Я хотел бы преобразовать его в класс Closure. Проблема в том, что я хотел бы повторно использовать (из соображений производительности) мой экземпляр класса Closure:

private Closure<Item, DateTime> closure = null;
public IEnumerable<Item> GetDayData(DateTime day)
{
    if (closure == null)
    {
        this.closure = new Closure<Item, DateTime>() {
            Variable = reference of "day" param, <=== HOW ????????
            Predicate = i => i.IsValidForDay(this.closure.Variable)
        }
    }
    this.items.Where(this.closure.Predicate);
}

Для того, чтобы я мог повторно использовать тот же экземпляр Closure, мне нужно было бы сохранить не значение параметра (в поле Variable замыкания), а указатель ссылки на параметр метода. Поэтому в следующий раз, когда этот метод будет вызван, замыкание Variable будет фактически указывать на правильное значение, которое будет использоваться.

Как я должен это сделать ?

Подобное происходит, когда компилятор генерирует классы, которые захватывают переменные, не содержащие лямбда-выражения. Я смотрел на декомпилированный код (используя Reflector), но, похоже, я не понимаю, как это делается ...

Ответы [ 3 ]

0 голосов
/ 02 декабря 2011
        List<PicInfo> pi = new List<PicInfo>();
        pi.Add(new PicInfo() { fileName = "a" });
        pi.Add(new PicInfo() { fileName = "a" });
        pi.Add(new PicInfo() { fileName = "b" });
        pi.Add(new PicInfo() { fileName = "b" });
        pi.Add(new PicInfo() { fileName = "a" });            

        string a = "a";
        Closure<PicInfo, string> cl = null;
        cl = new Closure<PicInfo, string>() { 
                  Variable = a, 
                  predicate = (i => i.fileName == cl.Variable.ToString()) 
        };
        MessageBox.Show(pi.Where(cl.predicate).Count().ToString()); // shows 3
        // Change variable value here
        string b = "b";
        cl.Variable = b;
        MessageBox.Show(pi.Where(cl.predicate).Count().ToString()); // Shows 2

@ Роберт Коритник, это работает для меня, with your existing class.Если вы хотите просто изменить переменную с существующим экземпляром, вы можете установить свойство Variable

0 голосов
/ 02 декабря 2011

Вы можете иметь ссылки на локальные переменные, но они являются управляемыми ссылками и не могут быть сохранены в полях.

Закрытия, которые компилятор генерирует, заменяя локальные переменные полями.Таким образом, это выглядит как локальная переменная на уровне C #, но как поле на уровне CLR.

Но в вашем случае я не вижу причин, почему вы хотели бы захватывать локальные переменные по ссылке.Так что должно быть возможно сделать что-то, как вы хотите:

public class Closure<TObject, TVariable>
{
    public TVariable Variable { get; set; }
    public Func<TVariable, TObject, bool> OpenPredicate { get; set; }

    public bool ClosedPredicate(TObject o)
    {
      return OpenPredicate(this, o);
    }
}

private Closure<Item, DateTime> closure = null;
public IEnumerable<Item> GetDayData(DateTime day)
{
    if (closure == null)
    {
        this.closure = new Closure<Item, DateTime>() {
            OpenPredicate = (closure, i) => i.IsValidForDay(closure.Variable)
        }
    }
    closure.Variable=day;
    this.items.Where(this.closure.ClosedPredicate);
}

Но я думаю, что этот код плохая идея.Если производительность настолько критична, выкиньте LINQ и сделайте это сами.Затраты на вызов делегата намного больше, чем стоимость создания объекта или двух.По моему опыту, LINQ обычно медленнее в 2-3 раза по сравнению с рукописным кодом.

Также этот код создает еще несколько объектов:

  • items.GetEumerator() возвращает один, если толькоэто тип значения или восстанавливает Enumerable.
  • Where возвращает новый IEnumerable
  • При перечислении результата вашей функции создается другой перечислитель.
  • Вы получаете новыйкаждый раз делегировать экземпляр

Таким образом, я считаю, что привязка к повторному использованию замыкания по соображениям производительности бесполезна.

0 голосов
/ 02 декабря 2011

Ваш пример немного ошибочен (также, вероятно, это не сработает, но семантика / намерение верны):

private Closure<Item, DateTime> closure = null;
public IEnumerable<Item> GetDayData(DateTime day)
{
    if (closure == null)
    {
        this.closure = new Closure<Item, DateTime>() {
            Predicate = i => i.IsValidForDay(this.closure.Variable)
        }
    }
    // assign here, else you only capture it the first time
    closure.Variable = day;
    this.items.Where(this.closure.Predicate);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...