Предотвратить .NET от "подъема" локальных переменных - PullRequest
7 голосов
/ 21 сентября 2008

У меня есть следующий код:

string prefix = "OLD:";
Func<string, string> prependAction = (x => prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

Поскольку компилятор заменяет переменную префикса закрытием, «NEW: brownie» выводится на консоль.

Есть ли простой способ запретить компилятору поднимать префиксную переменную, все еще используя лямбда-выражение? Я хотел бы, чтобы мой Func работал так же:

Func<string, string> prependAction = (x => "OLD:" + x);

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

Единственный способ обойти это, я вижу на данный момент, - создать новый сериализуемый класс, который хранит строку как переменную-член и имеет метод prepend строки:

string prefix = "NEW:";
var prepender = new Prepender {Prefix = prefix};
Func<string, string> prependAction = prepender.Prepend;
prefix = "OLD:";
Console.WriteLine(prependAction("brownie"));

С классом помощника:

[Serializable]
public class Prepender
{
    public string Prefix { get; set; }
    public string Prepend(string str)
    {
        return Prefix + str;
    }
}

Это кажется большой работой, чтобы компилятор был "тупым".

Ответы [ 9 ]

8 голосов
/ 21 сентября 2008

Теперь я вижу основную проблему. Это глубже, чем я думал. В основном решение состоит в том, чтобы изменить дерево выражений перед сериализацией, заменив все поддеревья, которые не зависят от параметров, константами. Это, по-видимому, называется «функлетизация». Вот объяснение этому здесь .

2 голосов
/ 21 сентября 2008

Просто сделайте еще одно закрытие ...

Скажи, что-то вроде:

var prepend = "OLD:";

Func<string, Func<string, string>> makePrepender = x => y => (x + y);
Func<string, string> oldPrepend = makePrepender(prepend);

prepend = "NEW:";

Console.WriteLine(oldPrepend("Brownie"));

Я еще не тестировал его, так как в данный момент у меня нет доступа к VS, но обычно я решаю эту проблему.

1 голос
/ 21 сентября 2008

Лямбды автоматически «засасывают» локальные переменные, боюсь, это просто, как они работают по определению.

0 голосов
/ 21 сентября 2008

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

public void static Func<string, string> MakePrependAction(String prefix){
    return (x => prefix + x);
}

(Обратите внимание на ключевое слово static.) Тогда лямбда не должна ссылаться на содержащий класс.

0 голосов
/ 21 сентября 2008

Что ж, если мы будем говорить о «проблемах» здесь, лямбды происходят из мира функционального программирования, а в чисто функциональном языке программирования нет назначений , и поэтому ваша проблема никогда не возникнет потому что значение префикса никогда не может измениться. Я понимаю, что C # думает, что это здорово - импортировать идеи из функциональных программ (потому что FP - это круто!), Но очень трудно сделать это красиво, потому что C # всегда был и всегда будет обязательным языком программирования.

0 голосов
/ 21 сентября 2008

Здесь уже есть несколько ответов, объясняющих, как вы можете избежать лямбды, «поднимающей» вашу переменную. К сожалению, это не решает вашу основную проблему. Неспособность сериализовать лямбду не имеет ничего общего с тем, что лямбда «подняла» вашу переменную. Если лямбда-выражению нужен экземпляр класса без сериализации, то вполне логично, что его нельзя сериализовать.

В зависимости от того, что вы на самом деле пытаетесь сделать (я не могу определиться с вашим постом), решение было бы перенести несериализуемую часть лямбды наружу.

Например, вместо:

NonSerializable nonSerializable = new NonSerializable();
Func<string, string> prependAction = (x => nonSerializable.ToString() + x);

использование:

NonSerializable nonSerializable = new NonSerializable();
string prefix = nonSerializable.ToString();
Func<string, string> prependAction = (x => prefix + x);
0 голосов
/ 21 сентября 2008

Это довольно распространенная проблема, т. Е. Переменные, изменяемые замыканием непреднамеренно - гораздо более простое решение:

string prefix = "OLD:";
var actionPrefix = prefix;
Func<string, string> prependAction = (x => actionPrefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

Если вы используете resharper, он на самом деле идентифицирует места в вашем коде, где вы рискуете вызвать непредвиденные побочные эффекты, такие как этот - так что если файл «весь зеленый», ваш код должен быть в порядке.

Я думаю, в некотором смысле было бы неплохо, если бы у нас был какой-то синтаксический сахар, чтобы справиться с этой ситуацией, чтобы мы могли записать его как однострочное, т.е.

Func<string, string> prependAction = (x => ~prefix + x);

Где какой-то префиксный оператор может привести к оценке значения переменной до создания анонимного делегата / функции.

0 голосов
/ 21 сентября 2008

Как насчет:

string prefix = "OLD:";
string prefixCopy = prefix;
Func<string, string> prependAction = (x => prefixCopy + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

0 голосов
/ 21 сентября 2008

Как насчет этого

string prefix = "OLD:";
string _prefix=prefix;
Func<string, string> prependAction = (x => _prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...