2009-06-05 17 views
6

Mi aplicación trata porcentajes mucho. Generalmente, estos se almacenan en la base de datos en su forma escrita en lugar de decimal (el 50% se almacenaría como 50 en lugar de 0.5). También existe el requisito de que los porcentajes estén formateados de forma consistente en toda la aplicación.Creando un tipo de porcentaje en C#

Para este fin he estado considerando crear una estructura llamada porcentaje que encapsula este comportamiento. Supongo que su firma sería algo como esto:

public struct Percentage 
{ 
    public static Percentage FromWrittenValue(); 
    public static Percentage FromDecimalValue(); 

    public decimal WrittenValue { get; set; } 
    public decimal DecimalValue { get; set; } 
} 

¿Es esto algo razonable de hacer? Verdaderamente encapsularía alguna lógica que se repite muchas veces, pero es lógico que peopel pueda entender. Supongo que necesito hacer que este tipo se comporte como un número normal tanto como sea posible, pero desconfío de crear conversiones implícitas a partir de un decimal en caso de que confundan más a las personas.

¿Alguna sugerencia de cómo implementar esta clase? o razones convincentes para no hacerlo.

Respuesta

5

Percentage La clase no debe preocuparse por el formateo de la interfaz de usuario. En su lugar, implemente IFormatProvider y ICustomFormatter para manejar la lógica de formateo.

En cuanto a la conversión, me gustaría ir con TypeConverter ruta estándar, lo que permitiría .NET para manejar correctamente esta clase, además de una clase PercentageParser utilidad independiente, lo que delegar las llamadas a TypeDescriptor que sean más utilizables en el código externo. Además, puede proporcionar el operador de conversión implicit o explicit, si es necesario.

Y cuando se trata de Percentage, no veo ninguna razón convincente para envolver simple decimal en un struct aparte de la expresividad semántica.

+0

Aprecio que la lógica debe ser proporcionada por otras clases. Pero en mi opinión todavía tiene mucho valor tener una clase de porcentaje que usa un método de ToString predeterminado que da una cadena de porcentaje atractiva. Especialmente porque esto necesita mostrarse en múltiples capas de presentación. (Una mezcla de Asp.Net, Winforms e informes en pdf). –

+0

ToString(), como se hace en otras clases .NET, puede delegar todo el trabajo al IFormatProvider y ICustomFormatter. Consulte DateTime.ToString para ver un ejemplo. –

+0

Por supuesto, las personas que siempre escriben un código perfecto nunca tienen que preocuparse por cuestiones tales como la expresividad semántica. Nunca volverán a mirar su código perfecto preguntándose qué demonios está pasando. - Si desea un motivo más específico, un tipo para Porcentaje podría ser extremadamente útil, siempre que lo haga una clase que encapsule un número decimal entre 0 y 1 inclusive. Esto se aplica muy bien a las aplicaciones de probabilidad.Usaría este tipo cada vez que tuviera que modelar un número en un rango delimitado, como la salida de un control deslizante en la interfaz de usuario o ponderaciones en una red neuronal. –

1

Creo que aquí puede mezclar la presentación y la lógica. Convertiría el porcentaje a una fracción decimal o flotante (0.5) al obtenerlo de la base de datos y luego dejaría que la presentación se ocupara del formato.

1

No crearía una clase separada para eso, esto solo crea más sobrecarga. Creo que será más rápido usar las variables double establecidas en el valor de la base de datos.

Si es de conocimiento público que la base de datos almacena porcentajes como 50 en lugar de 0.5, todos entenderán statemens como part = (percentage/100.0) * (double)value.

3

Recomiendo encarecidamente que use el tipo double aquí (no veo ningún uso para el tipo decimal tampoco, ya que en realidad no parece requerir precisión de base-10 en las posiciones decimales bajas). Al crear aquí un tipo Percentage, realmente está realizando una encapsulación innecesaria y haciendo que sea más difícil trabajar con los valores en el código. Si usa un double, que es habitual para los porcentajes de las historias (entre muchas otras tareas), encontrará que lidiar con el BCL y otros códigos es mucho más agradable en la mayoría de los casos.

La única funcionalidad adicional que puedo ver que necesita para porcentajes es la capacidad de convertir a/desde una cadena de porcentaje fácilmente. Esto se puede hacer de manera muy simple de todos modos utilizando líneas simples de código, o incluso métodos de extensión si desea abstraerlo ligeramente.

La conversión a cadena porcentaje:

public static string ToPercentageString(this double value) 
{ 
    return value.ToString("#0.0%"); // e.g. 76.2% 
} 

La conversión de cadena porcentaje:

public static double FromPercentageString(this string value) 
{ 
    return double.Parse(value.SubString(0, value.Length - 1))/100; 
} 
+0

Si se trata de una necesidad de conversión de cadenas fácilmente, la escritura de métodos de extensión podría ser útil. – RichardOD

+0

@RichardOD: Lees mi mente. Acababa de editar la publicación para dar ejemplos de métodos de extensión. – Noldorin

2

Parece razonable hacer algo, pero reconsideraría su interfaz para que sea más similar a otros tipos de primitivos CLR, p. algo como.

// all error checking omitted here; you would want range checks etc. 
public struct Percentage 
{ 
    public Percentage(decimal value) : this() 
    { 
     this.Value = value 
    } 

    public decimal Value { get; private set; } 

    public static explicit operator Percentage(decimal d) 
    { 
     return new Percentage(d); 
    } 

    public static implicit operator decimal(Percentage p) 
    { 
     return this.Value; 
    } 

    public static Percentage Parse(string value) 
    { 
     return new Percentage(decimal.Parse(value)); 
    } 

    public override string ToString() 
    { 
     return string.Format("{0}%", this.Value); 
    } 
} 

te había duda también quieren implementar IComparable<T> y IEquatable<T>, así como todos los operadores correspondientes y las anulaciones de Equals, GetHashCode, etc. Se podría también posible que también desee considerar la implementación de las interfaces y IConvertibleIFormattable.

Esto es mucho trabajo. Es probable que la estructura esté en la región de las 1000 líneas y tome un par de días (lo sé porque es una tarea similar a la estructura Money que escribí hace unos meses). Si esto tiene un costo beneficio para usted, entonces hágalo.

+0

Si insiste en adoptar este enfoque, también vale la pena sobrecargar a todos los operadores aritméticos y de comparación. – Noldorin

+0

@Noldorin - Um, dije "así como todos los operadores correspondientes e invalidaciones de Equals, GetHashCode, etc." en el contexto de esas interfaces ...? –

8

Estoy realmente un poco atónito por la actitud arrogante hacia la calidad de los datos aquí. Desafortunadamente, el término coloquial "porcentaje" puede significar una de dos cosas diferentes: una probabilidad y una varianza. El OP no especifica cuál, pero como la varianza suele calcularse, supongo que puede referirse al porcentaje como una probabilidad o fracción (como un descuento).

El muy buena razón para escribir una clase Percentage para este propósito no tiene nada que ver con la presentación, pero con asegurándose de que impidan que esos usuarios tontos tontos de hacer las cosas como la introducción de valores no válidos como -5 y 250.

Estoy pensando realmente más en una clase Probability: un tipo numérico cuyo rango válido es estrictamente [0,1]. Puede encapsular esa regla en un solo lugar, en vez de escribir código como este en 37 lugares:

public double VeryImportantLibraryMethodNumber37(double consumerProvidedGarbage) 
{ 
    if (consumerProvidedGarbage < 0 || consumerProvidedGarbage > 1) 
     throw new ArgumentOutOfRangeException("Here we go again."); 

    return someOtherNumber * consumerProvidedGarbage; 
} 

vez que tenga esta aplicación agradable. No, no es una mejora fantásticamente obvia, pero recuerde, usted está haciendo esa comprobación de valor cada vez que usa este valor.

public double VeryImportantLibraryMethodNumber37(Percentage guaranteedCleanData) 
{ 
    return someOtherNumber * guaranteedCleanData.Value; 
} 
+1

Sí, acabo de negar todo este hilo. ¿Quieres hacer algo al respecto? :) –