2009-03-13 9 views
5

Tengo un problema con una clase de contenedor algo simple que tengo.imitación de C# omitiendo el operador de asignación (=)

se ve algo como esto:

public class Wrapper<T> 
{ 
    private T _value; 

    public Wrapper<T>(T value) 
    { 
    _value = value; 
    } 

    public static implicit operator Wrapper<T>(T value) 
    { 
    return new Wrapper<T>(value); 
    } 

    public static implicit operator T(Wrapper<T> value) 
    { 
    return value._value; 
    } 
} 

He anulado los convertidores implícitos desde y hacia T, por lo que se comporta casi como una instancia de sí mismo t.

p. Ej.

Wrapper<int> foo = 42; 

Sin embargo tengo un pequeño problema al asignar una instancia de la envoltura a otro, ya que sólo desea asignar el valor de la segunda clase de contenedor.

Así que ahora, tengo que hacer esto:

Wrapper<int> foo = 42; 
Wrapper<int> bar = (int)foo; 

ni exponga _value públicamente a través de una propiedad.

Sin embargo, dado que esto está en una biblioteca, y no quiero que el usuario dependa de recordar esto, ¿tienen alguna idea de cómo podría imitar a la anulación del operador de asignación?

El problema solo con cambiar el puntero (como lo hace al asignar una instancia de clase a otra), es que tengo un diccionario de punteros a estos objetos Wrapper, así que no puedo tenerlos cambiando todo el tiempo, ya el diccionario dejaría de coincidir entonces.

puedo ver si esto es algo confuso, por lo que si me he dejado nada importante fuera, no dude en pedir :-)

+0

¿Esto fue resuelto alguna vez? Tengo un problema similar, excepto que estoy creando algo para ajustar los tipos básicos, p. flotante y desea poder hacer MyType < float > a = 1f; a = 2f; sin crear una nueva instancia ... ¿las referencias realmente se actualizan por magia? Solo lo intentaría, pero no estoy seguro de que sea un buen experimento, porque si funciona, podría ser una suerte ciega por lo que sé ... – jheriko

Respuesta

1

Si nos fijamos en anulable <T> ... lo que hace un muy Algo similar a lo que está haciendo aquí, expone el valor interno utilizando una propiedad .Value.

El problema en tan solo cambiando el puntero (como lo hace cuando se asigna una instancia de clase a otra), es que tengo un diccionario de punteros a estos objetos de envoltorio, así que no puedo tenerlos cambiando todo el tiempo , ya que el diccionario dejaría de coincidir entonces.

No estoy seguro de seguir esto, ¿qué es exactamente lo que almacena en el diccionario? Porque si está almacenando referencias, el CLR las actualizará según sea necesario.

+1

Según entendí, Nullable recibió algún tipo de soporte adicional para este tipo de tareas. escenarios. – ajlane

3

Puede hacer que Wrapper <T> a struct. Sin embargo, no estoy seguro si esto se adaptará al diseño de su aplicación o no.

5

Como el operador de asignación no se puede sobrecargar, no hay una solución realmente buena. Como alguien más señaló, usar una estructura te dará la semántica de la tarea que deseas, pero luego te enfrentarás a una semántica de valores, a menudo no es algo bueno.

Una opción es sobrecargar el constructor:

public Wrapper(Wrapper<T> w) 
{ 
    _value = w._value; 
} 

que daría lugar a la siguiente sintaxis:

Wrapper<int> foo = 42; 
Wrapper<int> bar = new Wrapper<int>(foo); 

Aunque más detallado de lo que tiene, se lee mejor.

O usted podría agregar un método Clone (no la interfaz ICloneable), por lo que se podría escribir:

Wrapper<int> bar = foo.Clone(); 

Usted podría ser realmente creativo y la sobrecarga de algún operador, por lo que es hacer prácticamente nada. No lo recomendaría, sin embargo. Usar la sobrecarga del operador para ese tipo de cosas generalmente hace que el código sea críptico y, a menudo, se rompe.

0

Esto es lo que son las propiedades. Le permiten definir qué significa la tarea. No puede definirlo para una clase o estructura en sí porque ya están definidas por el lenguaje para hacer las cosas necesarias. Solo agregue una propiedad Value a la clase.

Como alternativa, edite su pregunta para dar una descripción más amplia de su diseño y cómo encaja este Contenedor, ya que alguien puede sugerir un enfoque más simple.

0

Acabo de examinarlo, por lo que la clase una estructura realmente no es una opción, ya que tiene cierta lógica en el constructor sin parámetros, además hereda una clase abstracta, que contiene funciones abstractas internas.

No puedo usar una interfaz, ya que eso haría que esas funciones sean públicas, lo que rompería por completo la lógica.

Puedo publicar toda la clase si eso fuera útil, pero es algo largo (130 líneas) O podría lanzar en un servidor separado, si eso fuera mejor? (A pesar de que perjudica a la integridad de esta pregunta, como puedo eliminar con el tiempo a partir de ese servidor)

también explicar la clase es muy difícil, sin necesidad de escribir un ensayo completo: -/

De todos modos voy a tratar de ilustra el problema que estoy teniendo

adoptar 2 clases de mesa: CustomerTable y usertable:

public class CustomerTable 
{ 
    Wrapper<string> Name; 
} 

public class UserTable 
{ 
    Wrapper<string> Name; 
} 

Ahora el problema es que algún otro desarrollador, puede utilizar el código anterior de la siguiente manera:

CustomerTable customer = new CustomerTable(); 
UserTable user = new UserTable(); 
user.Name = customer.Name; // This breaks my internal dictionary 

lo que el desarrollador debe habían hecho , para que funcione, fue:

user.Name = (string)customer.Name; 

El problema es, sin embargo, quién en su sano juicio pensaría en eso, al escribir el código?

Incluso si he usado una propiedad Value, el desarrollador todavía tendría que recordar a escribir

user.Name = customer.Name.Value; // or user.Name.Value = .... 

Y de nuevo el desarrollador puede olvidar esto, y de repente se pone excepciones, o peor: los datos de los cuales no persiste en la base de datos.

Así que mi problema es realmente, que quiero que la envoltura sea completamente transparente (debe poder usarse como si fuera de hecho la clase/primitiva que está envolviendo). Sin embargo, al asignar desde un contenedor a otro, mi lógica interna se rompe.

Mucho trabajo de escritura, y mucho código. Avíseme si me exagero en la redacción.

+2

Usaría la propiedad Value. Imitar implícitamente un tipo en ambos sentidos nunca es una buena idea. Los desarrolladores descendentes aprenderán muy rápidamente cómo usar correctamente su biblioteca cuando sus asignaciones ingenuas no compilan. – ajlane

0

No implique implícitamente su contenedor de ambas formas.

public class DBValue<T> 
{ 
    public static implicit operator DBValue <T>(T value) 
    { 
     return new DBValue<T>(value); 
    } 

    public static explicit operator T(DBValue <T> dbValue) 
    { 
     return dbValue.Value; 
    } 

    private readonly T _value; 
    public T Value { get { this._value; } } 

    public DBValue(T value) 
    { 
     this._value = value; 
    } 
} 

Colada de DBValue<T> a T es una conversión con pérdida (como mínimo, se pierde el hecho de que es un valor de la base de datos), y por las mejores prácticas deben ser explícitos. Si no pierde nada lanzando desde DBValue<T> a T, también puede usar propiedades que devuelvan T.

Básicamente, ya has visto por qué no deberías intentar hacer esto: si se puede sustituir un DBValue por T y viceversa, ¿cómo sabe el compilador (o desarrollador) cuál elegir?

exigiendo a los promotores corriente abajo para escribir:

string value = MyProperty.Value 

o

string value = (string)MyProperty 

en lugar de

string value = MyProperty 

... no es tan onerosa, y se asegura de que todos saben exactamente lo que está pasando.

EDIT:

Para responder a la cuestión de hecho, no se puede anular la asignación de referencia - o hacer que parezca que tiene -, pero que en realidad no necesitan.

+0

Debo señalar que en realidad cometí este error un par de veces, sobre todo me causó problemas con los números: es decir, ¿cuál es el tipo de 1 + myValue si myValue es una clase que arroja el doble? – ajlane

0

A J Lane Ya veo lo que quieres decir, y creo que tienes razón, solo quería que sea lo más simple posible utilizar la biblioteca.

La razón de la conversión implícita de DbValue a T, es simplemente funciones que espera T.

por ejemplo

literalSomething.Text = Server.HtmlEncode(SomeTable.SomeStringColumn); 

en lugar de

literalSomething.Text = Server.HtmlEncode((string)SomeTable.SomeStringColumn); 

Esto requiere que el molde estar implícito

Dicho esto, acabo de leer su comentario mientras escribo esto, y puedo ver que ese es el problema.

Creo que volveré a exponer el valor a través de una propiedad, solo requiere que el desarrollador escriba más, y hace que el código sea feo, creo.

Imagínese DbValue:

if (someValue.Value.HasValue) // someValue is DbValue<int?> 

Pero, de nuevo es probablemente mejor con el código de "feo", que el código que se comporta de forma diferente a lo que cabría esperar por una simple lectura de la misma.

Supongo que esta pregunta termina realmente como una pregunta de "mejores prácticas".

Por lo tanto, para concluir, crearé una propiedad Value y la usaré en lugar de las versiones implícitas, y el desarrollador que use la biblioteca simplemente tendrá que vivir con eso.

Gracias por sus entradas :-)

+0

Para obtener un código más legible (menos "feo"), siempre puede asignar int? value = someValue.Value; y luego proceda como se esperaba. – ajlane

0

Este antiguos alambiques de post necesita información adicional para ser completa. Es evidente que no se puede lograr el comportamiento deseado original ya que el operador = no se puede sobrecargar, y del mismo modo C# no se puede "engañar" para convertir un objeto en su propio tipo ... siempre se reducirá a una asignación de referencia de clase. Pero las publicaciones posteriores de Steffen muestran que la clase Wrapper se usa no solo con variables locales, sino como un campo de clase tipo. Se puede usar la semántica deseada Y mantener la integridad del diccionario interno usando propiedades de clase en lugar de campos públicos.


Incluso manteniendo el original dado Wrapper<T> clase con sus dos operadores implícitos, aquí está el código que funcionaría:

public class CustomerTable 
{ 
    private Wrapper<string> _Name; 
    public Wrapper<string> Name { 
     get { return _Name; } 
     set { _Name = (string)value; } 
    } 
} 

public class UserTable 
{ 
    private Wrapper<string> _Name; 
    public Wrapper<string> Name { 
     get { return _Name; } 
     set { _Name = (string)value; } 
    } 
} 

Si se hiciera este cambio, no sería romper el código existente, ya que todavía permite varios modos de configurar la propiedad:

CustomerTable customer = new CustomerTable(); 
UserTable user = new UserTable(); 

user.Name = customer.Name; //*** No longer breaks internal data structures 

user.Name = "string literal"; // Works as expected with implicit cast operator 
user.Name = (string)customer.Name; // Still allowed with explicit/implicit cast operator 
user.Name = customer.Name.Value; // Also works if Value property is still defined 

Porque esto todavía no responde la pregunta original, el uso de la Contenedor la clase todavía podría ser problemática si se usa fuera del contexto de propiedad de la clase, es decir, pasada entre objetos, etc. Tal vez toda la clase Wrapper podría eliminarse con el diseño de clase adecuado, incluido el uso de elementos de acceso/obtención de propiedades.

Cuestiones relacionadas