2008-09-21 17 views
7

Mam następujący kod:Prevent z .NET „podnoszenia” zmiennych lokalnych

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

Ponieważ kompilator zastępuje zmienną prefiksu z zamknięciem „NEW: brownie” jest drukowany na konsoli.

Czy istnieje prosty sposób, aby uniemożliwić kompilatorowi podniesienie zmiennej prefiksu, nadal korzystając z wyrażenia lambda? Chciałbym sposób dokonywania moją pracę Func identycznie:

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

Powodem muszę to chciałbym serializacji wynikowy delegata. Jeśli zmienna przedrostkowa znajduje się w klasie niezdolnej do serializacji, powyższa funkcja nie będzie serializować.

Jedynym sposobem wokół to widzę w tej chwili jest stworzenie nowej klasy można zaszeregować który przechowuje ciąg jako zmienną Członkowskim oraz ma metodę ciąg poprzedzić:

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

z klasą pomocnika:

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

Wydaje się, że jest to bardzo dużo pracy, aby kompilator był "głupi".

+0

Twoje pytanie nie dotyczy leniwej oceny, ale serializacji? – Sam

Odpowiedz

8

Widzę teraz podstawowy problem. Jest głębszy niż początkowo sądziłem. Zasadniczo rozwiązaniem jest zmodyfikowanie drzewa wyrażeń przed serializowaniem go przez zastąpienie wszystkich poddrzew, które nie zależą od parametrów ze stałymi węzłami. Jest to najwyraźniej nazywane "funcletization". Istnieje wyjaśnienie tego here.

+0

Łącze msdn zostało zerwane; Myślę, że poprawny link jest teraz http://social.msdn.microsoft.com/Forums/en-US/67f63b9a-ea44-4428-aea0-5dcdb61e918b/binding-lambdas-when-they-are-closures Niestety kod tam używa FuncletExpression, klasy, która, jak sądzę, została usunięta z frameworka sieciowego. – HugoRune

+0

@ HugoRune: Dziękujemy za zaktualizowany link. Wyobrażam sobie, że kod działa, jeśli po prostu usuniesz skrzynkę dla FuncletExpression (i dodasz przypadki dla wszystkich typów ExpressionTypes, które zostały dodane od tego czasu). –

+0

Alternatywnie można to sprawdzić: http://dotnetinside.com/framework/v4.0.30319/System.Data.Linq/Funcletizer –

-1

Co o tym

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

Nie rozwiąże to problemu serializacji, ponieważ zmienna _prefix jest nadal dołączona do klasy zawierającej kod. – Brownie

-1

Jak o:

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

?

+0

Nie rozwiąże to problemu serializacji, ponieważ zmienna prefixCopy jest nadal dołączona do klasy zawierającej kod. – Brownie

1

Lambdas automatycznie "zasysa" zmienne lokalne, obawiam się, że to po prostu sposób, w jaki działają z definicji.

0

Jest to dość powszechny problem czyli zmienne są modyfikowane przez zamknięcie niezamierzone - o wiele prostsze rozwiązanie jest tylko, aby przejść:

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

Jeśli używasz ReSharper to rzeczywiście zidentyfikować miejsca w kodzie gdy istnieje ryzyko wystąpienia nieoczekiwanych skutków ubocznych, takich jak ten - jeśli więc plik jest "zielony", kod powinien być w porządku.

myślę, że w pewnym sensie byłoby miło, gdybyśmy mieli trochę cukru składniowej w tej sytuacji, więc możemy napisać ją jako jedną wkładką tj

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

Jeżeli jakiś operator prefiks spowodowałoby wartość zmiennej, która ma zostać obliczona przed skonstruowaniem anonimowego delegata/funkcji.

+0

Myślę, że przegapiłeś punkt pytania. Twój delegat nie będzie serializować, ponieważ zamknięcie zostało jeszcze utworzone, tylko na innej zmiennej. –

0

Istnieje już kilka odpowiedzi tutaj wyjaśniających, w jaki sposób można uniknąć lambda "podnoszenia" zmiennej. Niestety to nie rozwiązuje twojego podstawowego problemu. Niezdolność do serializowania lambda nie ma nic wspólnego z tym, że lambda "podniosła" zmienną. Jeśli wyrażenie lambda potrzebuje do obliczenia instancji klasy bez serializacji, ma to doskonały sens, że nie można jej serializować.

W zależności od tego, co faktycznie próbujesz zrobić (nie mogę zdecydować z twojego postu), rozwiązaniem byłoby przeniesienie nieusuwalnej części lambda na zewnątrz.

Na przykład zamiast:

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

zastosowanie:

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

Jeśli ten kod jest zawarty w klasie, która nie jest serializowalna (w moim przypadku jest to kodowanie kodu ASP.NET), wówczas funkcja prependAction nadal będzie odwoływać się do tej klasy i nie będzie przekształcana do postaci szeregowej. – Brownie

-1

Cóż, jeśli mamy zamiar mówić o "problemach" tu, lambdas pochodzą z funkcjonalnego programowania świecie, aw czysto funkcjonalne programowanie językowe, , nie ma zadań, więc twój problem nigdy by się nie pojawił, ponieważ wartość prefiksu nigdy by się nie zmieniła. Rozumiem, że C# uważa, że ​​fajnie jest importować pomysły z programów funkcjonalnych (ponieważ FP to fajne!), Ale bardzo trudno jest to zrobić ładnie, ponieważ C# jest i zawsze będzie imperatywnym językiem programowania.

0

Teraz pojawia się problem: lambda odnosi się do klasy zawierającej, której nie można przekształcić do postaci szeregowej. Następnie wykonaj coś takiego:

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

(Uwaga na słowo kluczowe statyczne). Następnie lambda nie musi odwoływać się do klasy zawierającej.

+0

Prawie zadziałało! Niestety kompilator wygenerował prywatną klasę zapieczętowaną do przechowywania zmiennej prefiksu. Klasy generowanej przez kompilator nie można szeregować. – Brownie

2

Wystarczy dokonać innego zamknięcia ...

Powiedz coś:

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

havn't testowałem go jeszcze tak nie mam dostępu do VS w tej chwili, ale normalnie, to jak rozwiązuję taki problem.

+0

Podczas wykonywania polecenia "OLD: Brownie" nadal jest tworzone zamknięcie i dlatego serializacja nadal nie działa. –

Powiązane problemy