Использование замыканий для отслеживания переменной: хорошая идея или подвох? - PullRequest
2 голосов
/ 26 декабря 2009

Хорошо, мне нужно иметь возможность отслеживать объекты типа значения, которые являются свойствами другого объекта, что невозможно сделать, если эти свойства не реализуют интерфейс IObservable или аналогичный. Затем я подумал о замыканиях и знаменитом примере от Джона Скита и о том, как это печатает 9 (или 10) кучу раз, а не возрастающий порядок чисел. Поэтому я подумал, почему бы не сделать это:

Class MyClass
{
    ...
    Func<MyValueType> variable;
    ...
    public void DoSomethingThatGetsCalledOften()
    {
        MyValueType blah = variable(); //error checking too not shown for brevity
        //use it
    }
    ...
}
... in some other consuming code ...
MyClass myClass = new MyClass();
myClass.variable = () => myOtherObject.MyOtherProperty;
//then myClass will get the current value of the variable whenever it needs it

Очевидно, что для этого потребуется некоторое понимание того, как работают замыкания, но мой вопрос таков: это хорошая идея или грязный взлом и неправильное использование системы замыканий?

Редактировать: Поскольку некоторые люди, похоже, неправильно понимают, что я пытаюсь сказать, вот консольная программа, которая демонстрирует это:

using System;
using System.Linq;

namespace Test
{
    class Program
    {
        public static void Main()
        {
            float myFloat = 5;
            Func<float> test = () => myFloat;
            Console.WriteLine(test());
            myFloat = 10;
            Console.WriteLine(test());
            Console.Read();
        }

    }
}

Это распечатает 5, затем 10.

Ответы [ 3 ]

8 голосов
/ 26 декабря 2009

Вы наткнулись на знаменитый коан: Замыкания - это объект бедняка. Вы используете Action<T> для замены объекта получения типа T. Такая вещь была бы (немного) менее грязной уловкой в ​​более динамичном языке, так как это могло бы быть реализовано путем внедрения метода получения, который украшен вашей функцией регистрации, но в C # нет элегантного способа обезопасить чье-то свойство, когда они этого не ожидают.

2 голосов
/ 26 декабря 2009

Будучи механизмом получения значения свойства, он будет работать (но не обеспечит никакого механизма быстрого уведомления об обновлениях).Однако это зависит от того, как вы собираетесь его использовать.Чтобы сделать это удобно , вам нужно будет использовать кучу лямбд в коде, или сделать код DynamicMethod / Expression во время выполнения.В большинстве случаев что-то более похожее на отражение было бы более удобным.

Я бы не стал беспокоиться об аспекте «тип значения»;в большинстве случаев это не является узким местом, несмотря на FUD - и, как правило, намного проще обрабатывать такой код с object, чем с помощью обобщений или аналогичных.

У меня есть код в моей IDEэто демонстрирует DynamicMethod против необработанного отражения (которое я собираюсь вскоре опубликовать в блоге), показывая, как код на основе отражения не должен быть медленным (или просто использовать HyperDescriptor).

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

1 голос
/ 26 декабря 2009

Вам нужно будет ввести variable член как Func<MyValueType> (или другой delegate, который возвращает MyValueType), но вы не сможете назначить значение blah таким образом. Как и при использовании замыкания в цикле foreach выше, будет оцениваться только в момент времени . Это не способ синхронизировать значение вашей переменной с другим объектом. На самом деле нет способа сделать это без:

  • непрерывный мониторинг значения свойства другого экземпляра в каком-то цикле, например Timer
  • реализация события уведомления об изменении в классе другого экземпляра

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

Редактировать

Я постараюсь сделать это немного яснее. Используя этот код, который вы разместили:

Class MyClass
{
    ...
    Action<MyValueType> variable;
    ...
    MyValueType blah = variable();
    //use it
    ...
}
...
MyClass myClass = new MyClass();
myClass.variable = () => myOtherObject.MyOtherProperty;

Во-первых, для того, чтобы это было функционально, variable должно быть Func<MyValueType>, а не Action<MyValueType> (Func возвращает значение, Action - нет; поскольку вы пытаетесь присвоить значение переменной , вам нужно выражение для возврата значения).

Во-вторых, главная проблема в вашем подходе - если я правильно читаю ваш код - вы пытаетесь присвоить значение переменной экземпляра blah оценочному значению variable() в классе декларация. Это не сработает по нескольким причинам:

  • присваивания внутри объявлений классов не могут получить доступ к членам экземпляра (который variable есть)
  • присваивание переменной в объявлении класса происходит только при создании объекта. Даже если бы присутствовало первое условие, вы просто получили бы NullReferenceException при создании экземпляра вашего объекта, поскольку он попытался бы оценить variable, что в то время было бы null
  • , даже если не учитывать первые два, значение blah все равно будет представлять только оценочное значение variable() в любое время, когда оно было оценено. Он не будет «указывать» на эту функцию и будет автоматически синхронизирован, как кажется, вы пытаетесь это сделать.

Если вам не нужна какая-то автоматическая синхронизация, то ничто не мешает вам просто оставить делегата Func<MyValueType> для оценки; в этом подходе нет ничего особенно хорошего или плохого, и он не будет закрытием, если только делегат (в вашем случае лямбда-выражение) не использует локальную переменную.

...