2012-06-21 7 views
11

Tengo dos tipos diferentes de cadenas que estoy pasando y usando en mi código, y las dos están estrechamente relacionadas, pero no deben confundirse entre sí. Pensé que podría ayudarme a evitar errores al tener dos clases que son solo cadenas, pero con nombres diferentes para que las firmas de métodos (y la incompatibilidad de tipos en general) refuercen el significado semántico de los dos tipos diferentes de cadenas. Al mismo tiempo, no quería tener que refactorizar desde something = "foo"; hasta something.Value = "foo"; en cien lugares.¿Cómo puedo declarar las clases de shell derivadas que no hacen más que actuar como cambiar el nombre?

primer pensamiento:

private class FirstKind : string { } 
private class SecondKind : string { } 

La idea es que si tengo

void MyMethod(FirstKind varOne, SecondKind varTwo) {...} 

, y luego tratar de llamarlo con MyMethod(secondKindVar, firstKindVar);, me gustaría obtener un error de compilación.

Pared I golpe: string está sellado.

Segundo pensamiento: crea la clase genérica KindOf<T> que no haría más que asimilar y escupir el valor con operadores de conversión implícitos. De esta manera:

private class KindOf<T> 
{ 
    public T Value { get; set; } 
    public KindOf() { Value = default(T); } 
    public KindOf(T val) { Value = val; } 
    public static implicit operator T(KindOf<T> kindOf) { return kindOf.Value; } 
    public static implicit operator KindOf<T>(T value) { return new KindOf<T>(value); } 
} 

Esta manera de que pudiera hacer algo como esto:

private class FirstKind : KindOf<string> { } 
private class SecondKind : KindOf<string> { } 

pared golpeo: nada de la clase KindOf<T> parece heredar: no existen ni los constructores ni los operadores en los tipos derivados, lo que significa Tengo que volver a implementar esencialmente toda la clase base en las clases derivadas. Copia de código. Yuck.

Así que me siento como si me falta algo básico aquí, y tal vez estoy fuera de las malas hierbas. ¿Alguna idea de lo que intento hacer?

+1

¿Quizás podría aclarar qué significa "dos tipos diferentes de cuerdas"? –

+1

Por encima de todo, tal vez solo necesites implementar los operadores y constructores implícitos en las subclases. Sí, algo así como copiar código, pero al menos no está empleando un proceso complicado o usando herencia cuando la herencia realmente no comunica nada _real_ (retóricamente, ¿qué es un "KindOf" de todos modos y realmente significa algo?) Y es solo para reducir la duplicación de código entre clases que no tienen nada en común además de la sintaxis similar (pero diferentes usos) –

+0

De acuerdo, las clases realmente no deberían estar en la misma jerarquía de herencia, especialmente considerando que no * desea * referirse a ellas por un tipo común, quieres que sigan siendo distintos. – Servy

Respuesta

0

Haría esto por las cuerdas.

abstract class MyTypedString 
{ 
    protected MyTypedString(string value) 
    { 
    Value = value; 
    } 
    public string Value { get; private set; } 
} 

Posteriormente, se podría terminar esta clase abstracta con su FirstKind y SecondKind. Esto mantendrá el objeto inmutable como lo son las cuerdas. No daría esto ningún operador de conversión implícito.

+0

Eso es esencialmente lo mismo que su segundo ejemplo (solo menos agradable). –

0

Los operadores seguirán funcionando, sólo tiene que hacer referencia a la .Value miembro de su clase Kindof:

FirstKind stringFoo = new FirstKind("foo"); 
FirstKind stringQux = new FirstKind("qux"); 

// String concatenation operator 
FirstKind stringTar = new FirstKind(stringFoo.Value + stringQux.Value); 

Sin embargo, como usted dice, no habrá ningún "tipo de comprobación" si se mezclan los dos, por ejemplo,

SecondKind stringBar = new SecondKind("bar"); 
FirstKind stringHal = new FirstKind(stringFoo.Value + stringBar.Value); // this will succeed, even though you intend stringFoo and stringBar to be incompatible 

medida que los operadores son parte de una clase de interfaz, entonces debe volver a ponerlas en práctica - sin embargo, sólo se puede envolver el contenido de clase aplicación, por lo que sólo necesita hacer aburrida ronco-trabajo para conseguir que esto funcione .

0

Puede intentar sobrecargar el operador de conversión implicit de su clase en una cadena. Entonces usted no debería tener que actualizar sus referencias a .Value. Las clases deberán permanecer separadas y deberá crear constructores para ellas que acepten el valor de la cadena. La mejor práctica sería mantener la cadena readonly para que la clase sea inmutable. Imitar la clase de cuerda que debe ser una representación de.

public class FirstKind 
{ 
     private readonly string value; 
     public FirstKind(string v) { this.value = v }; 
     public static implicit operator string(FirstKind v){ return v.value; } 
} 

public class SecondKind 
{ 
     private readonly string value; 
     public SecondKind (string v) { this.value = v }; 
     public static implicit operator string(SecondKind v){ return v.value; } 
} 
+1

Él ya tiene ... – Servy

+0

Publicada accidentalmente antes de terminar. – Fr33dan

+0

Él ya lo está haciendo también ... – Servy

0

Casi puede usar su clase genérica. Solo el lanzamiento implícito desde una cadena (u otro tipo) a la clase derivada nunca puede funcionar, porque la clase base no puede saber si la cadena debe ser convertida a firstkind o secondkind. Para que una parte debe ser copiado, el resto sin embargo puede permanecer en la clase base:

private class FirstKind : KindOf<string> 
{ 
    public static implicit operator FirstKind(string value) { return new FirstKind{Value = value}; } 
} 
private class SecondKind : KindOf<string> 
{ 
    public static implicit operator SecondKind(string value) { return new SecondKind{Value = value}; } 
} 

La conversión a la clase base todavía se puede hacer desde dentro de la clase genérica:

 FirstKind Foo = "Foo"; //via implicit operator in FirstKind 
     string stringFoo = Foo; //via implicit operator in baseclass 
+0

El problema que queda, sin embargo, es que no se puede hacer algo como 'Foo.Length'. Tendría que arreglar todo su código para hacer cosas como 'Foo.Value.Length' que probablemente no valga la pena. –

+0

Sí, eso a su vez requeriría una clase KindOfString intermedia que hereda de KindOf e implementa esas propiedades, de las cuales los otros tipos heredan (o si solo se usan cadenas de todos modos, omita la clase genérica alltogether). –

3

para ser honesto, No usaría los operadores implícitos en absoluto, o cualquier herencia para ese asunto. La idea es tener un método que tome FirstKind. Con el operador implícito, puede pasar una cadena y se convertirá; esto significa que no está forzando al código a declarar explícitamente que es un primero/segundo tipo. Me quedaría con dos clases que tienen un solo constructor tomando una cadena y una propiedad de solo lectura. Estás sobrecomplejándolo añadiendo algo más.

Si en dicho bien inmueble está volviendo molesto, que posiblemente pudo ver la conversión implícita a cadena, desde cada una de las clases personalizadas, pero yo creo que una conversión implícita de cadena sería perjudicial . Si bien estoy de acuerdo en que generalmente se debe evitar copiar y pegar código, cuando se trata literalmente de una sola línea de código, copiar y pegar esa conversión implícita en ambas clases será más fácil, más efectivo y generalmente más fácil de mantener que intentar usar herencia o cualquier otra cosa. para evitar escribirlo dos veces

+0

+1 Desafortunadamente, dado que 'String' está sellado, me temo que esta es probablemente la mejor opción. –

+0

Estamos de acuerdo en esto. "La conversión implícita a la secuencia" fue la idea detrás de la mía, pero aún mantiene las clases separadas, como sugeriría. – Fr33dan

1

Voy a hacer una respuesta a mi comentario porque creo que es una sola.

En la parte superior de mi cabeza, es posible que solo necesites implementar los operadores y constructores implícitos en las subclases. Sí, algo así como copiar código, pero al menos no está empleando procesos intrincados o usando herencia cuando la herencia realmente no comunica nada real (retóricamente, ¿qué es un "KindOf" de todos modos y realmente significa algo?) Y es solo allí para reducir la duplicación de código en todas las clases que no tienen nada en común además de sintaxis similar (pero diferentes usos)

public class FirstKind 
{ 
    public string Value { get; private set; } 

    public FirstKind(string value) 
    { 
     this.Value = value; 
    } 

    public static implicit operator FirstKind(string value) 
    { 
     return new FirstKind(value); 
    } 

    public static implicit operator string(FirstKind value) 
    { 
     return value.Value; 
    } 
} 

Además, es posible que desee considerar la posibilidad de la clase inmutable, si se supone que deben representar cadenas. (Otro detalle de implementación que podría indicar quizás estos objetos no deberían heredarse de una clase reutilizada de KindOf que, en su implementación, los hace mutables siempre)

No hay mucho cableado y deja su código abierto para cambios en el futuro (quizás desee agregar una nueva propiedad? Agregue algún código de validación?) y los consumidores de la API realmente no se preocupan de que nada sea KindOf solo para ahorrar en algunos menor duplicación de código. (Tal vez puede hacer que un fragmento de hacer su vida un poco más fácil?)

1

En su pregunta, FirstKind y SecondKind son los tipos de variables similares a cuerdas, y string es el argumento de características de su clase KindOf<T>.Un enfoque diferente (lo que evita la duplicación de código requerido por las otras respuestas) es darle la vuelta al papel de FirstKind y SecondKind, haciéndolos vacían clases "marcadores" que se utilizan como argumentos de tipo de un tipo String<TKind>:

public class FirstKind { } 
public class SecondKind { } 

public struct String<TKind> 
{ 
    private readonly string _value; 

    public String(string value) 
    { 
     _value = value; 
    } 

    public override string ToString() 
    { 
     return _value; 
    } 

    public static implicit operator String<TKind>(string s) // see the note below 
    { 
     return new String<TKind>(s); 
    } 

    public static implicit operator string(String<TKind> s) 
    { 
     return s.ToString(); 
    } 

    public static String<TKind> operator +(String<TKind> s1, String<TKind> s2) 
    { 
     return new String<TKind>(s1.ToString() + s2.ToString()); 
    } 

    public int Length 
    { 
     get { return _value.Length; } 
    } 

    // You can add methods like Substring and Trim that delegate to _value. 
} 

ahora se puede declarar MyMethod así:

void MyMethod(String<FirstKind> varOne, String<SecondKind> varTwo) 
{ 
    varOne += varOne; // OK 
    varTwo += varTwo; // OK 
    varOne = varTwo; // ERROR 
} 

Nota: he dejado en el operador de conversión implícita de una llanura string. Es posible que desee considerar eliminarlo, forzando código para identificar explícitamente el tipo de cadena: new String<FirstKind>("...").

+0

Hm, enfoque interesante. Tendré que considerar esto. – Atario

Cuestiones relacionadas