Отложенное выполнение в C # - PullRequest
12 голосов
/ 10 сентября 2009

Как я могу реализовать свой собственный механизм отложенного выполнения в C #?

Например, у меня есть:

string x = DoFoo();

Можно ли выполнить магию, чтобы DoFoo не выполнялся, пока я не "использую" x?

Ответы [ 6 ]

18 голосов
/ 10 сентября 2009

Вы можете использовать лямбды / делегаты:

Func<string> doit = () => DoFoo();
//  - or -
Func<string> doit = DoFoo;

Позже вы можете вызвать doit так же, как метод:

string x = doit();

Я думаю, что самое близкое, что вы можете получить, это что-то вроде этого:

Lazy<string> x = DoFoo;

string y = x; // "use" x

С определением Lazy<T> похожим на это (не проверено):

public class Lazy<T>
{
    private readonly Func<T> func;
    private bool hasValue;
    private T value;

    public Lazy(Func<T> func)
    {
        this.func = func;
        this.hasValue = false;
    }

    public static implicit operator Lazy<T>(Func<T> func)
    {
        return new Lazy<T>(func);
    }

    public static implicit operator T(Lazy<T> lazy)
    {
        if (!lazy.hasValue)
        {
            lazy.value = lazy.func();
            lazy.hasValue = true;
        }
        return lazy.value;
    }
}

К сожалению, похоже, что алгоритмы вывода типов компилятора не могут автоматически определить тип Func<T> и поэтому не могут сопоставить его с оператором неявного преобразования. Нам нужно явно объявить тип делегата, что делает операторы присваивания более многословными:

// none of these will compile...
Lazy<string> x = DoFoo;
Lazy<string> y = () => DoFoo();
Lazy<string> z = delegate() { return DoFoo(); };

// these all work...
Lazy<string> a = (Func<string>)DoFoo;
Lazy<string> b = (Func<string>)(() => DoFoo());
Lazy<string> c = new Func<string>(DoFoo);
Lazy<string> d = new Func<string>(() => DoFoo());
Lazy<string> e = new Lazy<string>(DoFoo);
Lazy<string> f = new Lazy<string>(() => DoFoo);
6 голосов
/ 10 сентября 2009

Один из вариантов - использовать класс Lazy<T>, который раньше был из библиотеки параллельных расширений, а теперь является частью .Net Framework 4.0.

Позволяет задерживать данные процесса с учетом потоков.

3 голосов
/ 10 сентября 2009

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

public IEnumerable<int> DoFoo() {
   Console.WriteLine("doing foo");
   yield return 10;
}

[Test]
public void TestMethod()
{
    var x = DoFoo();
    Console.WriteLine("foo aquired?");
    Console.WriteLine(x.First());
}
1 голос
/ 10 сентября 2009

Вместо передачи строки x , передайте делегат, который доставит вам строку

Func<String> fooFunc=()=>DoFoo();
0 голосов
/ 10 сентября 2009

Вы в значительной степени описываете LINQ в действии. Запрос linq описывает, как получить данные, но данные извлекаются (вызывается DoFunc) только тогда, когда запрос повторяется. Подумайте, можете ли вы изменить свой дизайн, чтобы принять IQueryable<string> там, где вам нужно string.

0 голосов
/ 10 сентября 2009

Почему бы просто не вызвать 'DoFoo ()', пока вы не захотите?

- Правка

Я имею в виду, что вы имеете в виду "использовать"

Например, если вы хотите, чтобы он вызывался при вызове '.ToString ()', вы всегда можете наследовать класс и реализовывать там свою функцию (но это было бы совершенно не интуитивно понятно ИМХО).

...