2010-02-16 10 views
5

¿Tiene C# una manera de cambiar temporalmente el valor de una variable en un ámbito específico y revertirlo de nuevo automáticamente al final del alcance/bloque?¿Tiene C# una manera de imitar la memoria transaccional de software, en una escala pequeña?

Por ejemplo (no código real):

bool UpdateOnInput = true; 

using (UpdateOnInput = false) 
{ 
    //Doing my changes without notifying anyone 
    Console.WriteLine (UpdateOnInput) // prints false 
} 
//UpdateOnInput is true again. 

EDIT:

La razón por la que quiero lo anterior se debe a que no quiero hacer esto:

UpdateOnInput = false 

//Doing my changes without notifying anyone 
Console.WriteLine (UpdateOnInput) // prints false 

UpdateOnInput = true 
+0

Software Transactional Memory? – shahkalpesh

+4

es triste ver que tienes una muy buena reputación y todavía publicas toda la pregunta como título. – Shoban

+2

¿Puede explicar en qué necesita específicamente esta construcción? Esto sucede para las variables pasadas por valor automáticamente, como al hacer llamadas a funciones, pero no estoy seguro de que sea eso lo que estás buscando. – GrayWizardx

Respuesta

13

No, no hay manera de hacer esto directamente. Hay algunas diferentes escuelas de pensamiento sobre cómo hacer este tipo de cosas. Comparar y contrastar estos dos:

originalState = GetState(); 
SetState(newState); 
DoSomething(); 
SetState(originalState); 

vs

originalState = GetState(); 
SetState(newState); 
try 
{ 
    DoSomething(); 
} 
finally 
{ 
    SetState(originalState); 
} 

Mucha gente le dirá que el último es "más seguro".

No es necesariamente así.

La diferencia entre los dos es, por supuesto, que este último restablece el estado incluso si DoSomething() arroja una excepción. ¿Es mejor que mantener el estado mutado en un escenario de excepción? ¿Qué lo hace mejor? Tiene una excepción inesperada no controlada que informa que algo horrible y inesperado ha sucedido.Su estado interno podría ser completamente inconsistente y arbitrariamente desordenado; nadie sabe lo que podría estar sucediendo en el momento de la excepción. Todo lo que sabemos es que DoSomething probablemente estaba tratando de hacer algo para el estado mutado.

¿Es realmente lo correcto en el escenario donde algo terrible y desconocido ha sucedido para seguir revolviendo esa olla en particular y tratando de mutar el estado que acaba de causar una excepción otra vez?

A veces eso va a ser lo correcto y, a veces, empeorará las cosas. El escenario en el que estás realmente depende de lo que esté haciendo exactamente el código, así que piensa cuidadosamente qué es lo correcto antes de elegir ciegamente uno u otro.

Francamente, prefiero resolver el problema al no entrar en la situación en primer lugar. Nuestro diseño de compilador existente utiliza este patrón de diseño, y francamente, es muy irritante. En el compilador de C# existente, el mecanismo de informe de errores es "de efecto secundario". Es decir, cuando una parte del compilador recibe un error, llama al mecanismo de informe de errores que luego muestra el error al usuario.

Este es un problema importante para la unión de lambda. Si usted tiene:

void M(Func<int, int> f) {} 
void M(Func<string, int> f) {} 
... 
M(x=>x.Length); 

entonces la forma en que esto funciona es que tratar de unir

M((int x)=>{return x.Length;}); 

y

M((string x)=>{return x.Length;}); 

y ver cuál, en su caso, nos da un error. En este caso, el primero da un error, este último compila sin error, por lo que esta es una conversión lambda legal y la resolución de sobrecarga tiene éxito. ¿Qué hacemos con el error? No podemos informarlo al usuario porque este programa está libre de errores.

Por lo tanto, lo que hacemos cuando vinculamos el cuerpo de una lambda es exactamente lo que dice: le decimos al reportero de errores "no informe sus errores al usuario; guárdelos en este búfer aquí". Luego vinculamos el lambda, restauramos el informe de errores a su estado anterior y miramos el contenido del buffer de error.

Podríamos evitar este problema completamente cambiando el analizador de expresiones para que devolviera los errores junto con el resultado, en lugar de convertir los errores en un efecto secundario relacionado con el estado. Entonces la necesidad de mutación del estado de informe de error desaparece por completo y ni siquiera tenemos que preocuparnos por ello.

Así que le animo a que vuelva a visitar su diseño. ¿Hay alguna manera de hacer que la operación que estás realizando no dependa del estado en el que estás mutando? Si es así, hazlo y no tendrás que preocuparte por cómo restaurar el estado mutado.

(Y, por supuesto, en nuestro caso nos hacemos desea restaurar el estado a una excepción. Si algo dentro del compilador lanza durante la unión lambda, queremos ser capaces de informar que para el usuario! Nosotros no hacemos quiere que el reportero de errores permanezca en el estado "suprimir los errores de informe".)

+1

De la manera en que lo veo, si se espera la excepción, por lo tanto, el bloque 'finally' debe ejecutarse y deshacer cualquier cambio de estado para restablecer un conocido buen estado. Si la excepción es inesperada, no debería ser manejada, y el programa debería terminar sin ejecutar finalmente los bloques: esto se puede arreglar manejando 'AppDomain.UnhandledException' y llamando a' Environment.FailFast'. Con este enfoque, los bloques 'finalmente' son siempre obvios: pueden escribirse con seguridad para desenrollar el estado según sea necesario. El factor de control sobre si se ejecutan es si alguna vez se captura una excepción o no. –

+4

Algún día alguien debería intentar realizar una ingeniería inversa del compilador de C# a partir de las pequeñas pepitas que mencionas aquí y en tu blog ... –

+0

Gracias Eric. En mi ejemplo, digamos que para un sistema que detecta algún cambio, y las cosas que hace el método no deberían detectarse, ¿cómo lo diseñaría si el método no se basa en ese estado que usa el change_detection_system? ¿Requiere un pensamiento muy diferente? –

5

n , tienes que hacerlo manualmente con un bloque try/finally. Me atrevo a decir que podría escribir una implementación IDisposable que haría algo raro junto con expresiones lambda, pero sospecho que un bloque try/finally es más simple (y no lo hace abuse the using statement).

12

No, pero es bastante simple de hacer esto simplemente:

bool UpdateOnTrue = true; 

// .... 

bool temp = UpdateOnTrue; 
try 
{ 
    UpdateOnTrue = false; 
    // do stuff 
} 
finally 
{ 
    UpdateOnTrue = temp; 
} 
4

Suena como si realmente quiere

Stack<bool> 
+0

Puedes ir un paso más y poner el código en una función y pasar la variable como parámetro, la pila se gestiona para usted sin pensarlo. – AaronLS

+1

Esto es demasiado simplista. La variable que se está configurando puede ser un campo en lugar de una variable local, el punto es detener la reentrada en el bloque de código. A veces está bien ignorar un mensaje (por ejemplo, un evento de movimiento del mouse) debido a que un objeto está "en uso" cuando llegó el mensaje. –

1

en latas ... lo dudo. Dado que su ejemplo es un simple uso de un valor bool temporal Estoy asumiendo que tienes algo loco en cuenta :-) Puede implementar algún tipo de pila stucture:

1) Empuje antiguo valor en la pila

2) Cargar nuevo valor

3) hacer cosas

4) Pop de la pila y reemplace el valor usado.

bruto (También conocido como no probado) ejemplo (No se puede mirar hacia arriba sintaxis pila en este momento)

bool CurrentValue = true; 
Stack<bool> Storage= new Stack<bool> 

Storage.Push(CurrentValue); 
CurrentValue=false; 
DoStuff(); 
CurrentValue = Storage.Pop(); 
//Continue 
+0

Gracias, pero por viejo valor ¿te refieres a lo falso o verdadero en mi ejemplo? ¿Puedes mostrar esto en el código? –

+1

@Joan Venge Agregué un ejemplo de código aproximado para mostrar de qué estoy hablando –

6

Probar:

public void WithAssignment<T>(ref T var, T val, Action action) 
{ 
    T original = var; 
    var = val; 
    try 
    { 
     action(); 
    } 
    finally 
    { 
     var = original; 
    } 
} 

Ahora usted puede decir:

bool flag = false; 

WithAssignment(ref flag, true,() => 
{ 
    // flag is true during this block 
}); 

// flag is false again 
+0

+1 Exactamente lo que debería haber escrito. Combinación perfecta de expresiones 'ref' y lambda. –

+0

Gracias, buen ejemplo. –

1

Debe refactorizar el código para utilizar una función separada, así:

bool b = GetSomeValue(); 
DoSomething(ModifyValue(b)); 
//b still has the original value. 

Para que esto trabaje para un tipo de referencia, necesita copiarlo antes de jugar con él:

ICloneable obj = GetSomeValue(); 
DoSomething(ModifyValue(obj.Clone())); 
//obj still has the original value. 

Es difícil escribir el código correcto cuando los valores de las variables cambian mucho. Esfuércese por tener la menor cantidad de reasignaciones posibles en su código.

Cuestiones relacionadas