2010-06-25 12 views
6

Imagino la capacidad de escribir código fluido que agrega significado a los números dentro de las bases de código. Supongamos que quiere un número para representar una distancia en millas. Usted tendría algo como:Método de extensión para Int32 en C#

Uso:

var result = myMethod(100.Miles()); 

Creo que esto sería mucho más fácil de leer que simplemente pasando en el int, además de que presumiblemente podría aplicar la comprobación de límites al tipo de Miles.

método de extensión y estructura aplicación:

static class IntExtensions 
{ 
    public static Miles(this int i) { get { return new Miles { Count = i }; } } 
} 

public struct Miles 
{ 
    public int Count { get; private set; } //optionally perform bounds checking 
} 

¿Sería tal idea sea útil, o es demasiado tarde de un viernes caliente?

Editar: Sí, no se ve tan bien sin propiedades de extensión ... Disculpas por el código inválido apresurado. Sólo era una idea.

+3

Su método de extensión y su 'definición de clase es incorrecta. –

+1

¿Cómo imaginas usar esto? ¿Agregar cosas para todos los tipos de unidades? 100.Miles() * 2.43.Kilograms()/454.MoonCycles()? – simendsjo

+0

quiere decir que "envision" no "prevé", y aunque puede ser genial para su implementación singular, no tendré nada de eso en mi aplicación financiera. Quiero .Dollars y .Euros. – StingyJack

Respuesta

4

Es una idea interesante, pero me gustaría pedir un caso de uso muy fuerte antes de hacer algo como esto. Por un lado, una vez que un número se convierte en una "milla", ya no se puede tratar como un int. O bien debe implementar toda la gama de operadores, o bien devolver las millas a enteros antes de realizar operaciones aritméticas sobre ellos. Es mucho trabajo extra si no hay una buena razón para hacerlo.

Sin embargo, en algunos casos esta sería una buena estrategia. Creo que recuerdo haber oído hablar de un cohete multimillonario o algo que falló NASA una vez lost a $125 million space ship porque los programadores estaban pasando las unidades de medida incorrectas a una función. Esto te ayudaría a evitar ese problema.

En una nota relacionada, puede que le interese F #, que tiene built-in support for units of measure.

2

Su estructura Miles debe ser inmutable.

cambiarlo a

public struct Miles { 
    public Miles(int count) : this() { Count = count; } //optionally perform bounds checking 

    public int Count { get; private set; } 
} 
2

Un comentario: ¿cuál es el punto de hacer Miles mutable? Un int no es mutable, ¿por qué hacerlo mutable una vez que tiene una unidad?

(Adicionalmente, han propiedades de extensión se introducirá en C# 4? De lo contrario, esto no funcionará.)

Por último, si desea agregar unidades, deben hacerse componibles, y yo En este momento no ver cómo lograr esto.

Por ejemplo, el siguiente código debe compilar:

var x = 100.km; 
var y = 10.sec; 
var kmh = x/y; // What type does kmh have? 

En C++, hay una biblioteca que implementa esta mediante la representación de los tipos como tuplas de las dimensiones de todas las siete unidades físicas fundamentales, pero esto no lo hace trabajar en C# ya que requiere enteros como argumentos de plantilla.

0

Personalmente, no estoy viendo un punto.

veo ninguna razón la firma de myMethod no debe ser:

public object MyMethod(int miles) 
{ 
    // bounds checking on int here 
    // then logic 
} 

También es posible usar los contratos de código para hacer las cosas aún más explícito.

Agregar una llamada a .Miles() y hacer el int Mutable es más confuso.

+2

"También podría usar Contratos de código para hacer que las cosas sean aún más explícitas." Pero el uso de tipos en lugar de contratos es aún más explícito y ofrece una mejor compatibilidad con el compilador. Si considera el sistema de tipo como un sistema de prueba que le permite formular aserciones e invariantes sobre su código (y realmente, esa es la * definición * de un sistema de tipo), entonces agregar unidades tiene mucho sentido. Además del hecho de que los contratos de código en el sitio de llamada requieren más duplicación de código por el momento. –

+0

@Konrad Rudolph: cuando sugerí Contratos de código, me refería a la verificación de validación de lógica y validación. El uso de contratos de código le permitiría definir mejor el rango de valores esperados y permitir que el compilador advierta a los desarrolladores cuando sus valores pueden estar fuera del rango. Acepto que un tipo es más explícito, pero en este caso es un envoltorio simple alrededor de Int32 que no agrega valor en mi opinión. –

+0

"es una envoltura simple ... que no agrega valor" revela una incomprensión de los tipos que existen. Los tipos no tienen que agrupar datos o agregar mucha acción. El mero hecho de que existan * tipos * diferentes es un propósito importante de los tipos. ¿Por qué más hay diferentes unidades en física? Cualquier físico le dirá que cuando hace un cálculo (en papel o en la computadora) y las unidades al final están de acuerdo, el cálculo probablemente también sea correcto. El mismo argumento se aplica a la verificación de hechos (= compilación con tipos fuertes). Considere: ¿por qué más existe la clase 'TimeSpan'? –

1

Así es como debería ser su diseño.

Tenga en cuenta que, aún no tenemos propiedades de extensión en C#, solo son métodos de extensión.

class Program 
{ 
    static void Main(string[] args) 
    { 
     var result = myMethod(100.ToMiles()); 
     //Miles miles = 100.ToMiles(); 
    }   
} 

static class IntExtensions 
{ 
    public static Miles ToMiles(this int miles) 
    { 
     return new Miles(miles); 
    } 
} 

struct Miles 
{ 
    public int Count { get; private set; } 

    public Miles(int count) 
     : this() 
    { 
     if (count < 0) 
     { 
      throw new ArgumentException("miles type cannot hold negative values."); 
     } 
     this.Count = count; 
    } 
} 
+1

¿Por qué el prefijo redundante 'To'? '100.Miles()' será perfectamente claro e inequívoco. –

+1

El método debe designar un nombre que transmita su propósito. ToMiles() transmite el mensaje de que este método convierte 'int' a Miles. Al igual que cualquier tipo de .Net, int.ToString() convierte el valor int en su representación de cadena. –

+1

Estoy de acuerdo con el concepto '.ToMiles()'. Se ajusta a otros métodos, como 'ToString()' o los diversos métodos de 'Convert'. –

4

Usted no debería tener magic numbers en su código, for good reason, y escribir un método de extensión no hace mucho para aliviar el problema.Aún tienes un número mágico flotando alrededor.

Si es constante, hazlo constante e incluye las _ MILLAS _ en el nombre de la constante.

Además, ¿por qué no ajustar el valor en una clase o estructura llamada Distancia que simplemente contiene un valor numérico y también una enumeración que especifica la unidad de medida?

Algo así como:

public class Distance { 
    private double _distanceValue; 
    private UnitOfMeasure _uom; 

    public double DistanceValue { 
     get { return _distanceValue; } 
     set { _distanceValue = value; } 
    } 

     public UnitOfMeasure Uom { 
     get { return _uom; } 
     set { _uom = value; } 
    } 
} 

public enum UnitOfMeasure { 
    Kilometers, 
    Miles, 
    Feet, 
    Inches, 
    Parsecs 
} 
+1

Si bien no soy fanático de los números mágicos, creo que hay un caso donde esto tiene sentido, y es cuando se usa este patrón para crear una estrategia en un patrón de estrategia.En el dominio donde lo uso, es importante poder crear nuevas estrategias en código rápidamente y conectarlas al marco de proceso. Dado que la estrategia incorpora lógica de dominio, es natural poner el valor necesario para implementar la estrategia en el código. Este enfoque hace que esto sea muy fácil de escribir y, lo que es más importante, leer. – codekaizen

+0

Esto me parece bastante bien: var result = myMethod (new Distance (100, UnitOfMeasure.Miles)); Además, imagínese si su clase Distancia tiene un método ConvertTo (UnitOfMeasure), y mejor aún, fue dictada por una clase base y heredada por las clases Mass, Weight etc. –

+1

"por qué no ajustar el valor en una clase o estructura llamada Distancia ..." porque luego pierde cualquier comprobación en tiempo de compilación. –

0
public static class Int32Extensions 
{ 
    public static Miles ToMiles(this Int32 distance) 
    { 
     return new Miles(distance); 
    } 
} 

public class Miles 
{ 
    private Int32 _distance; 

    public Miles(Int32 distance) 
    { 
     _distance = distance; 
    } 

    public Int32 Distance 
    { 
     get 
     { 
      return _distance; 
     } 
    } 
} 
2

utilizo esta misma idea de crear una especie de gramática interna para un proyecto que se ocupa de las medidas físicas. Yo tenía dudas sobre este enfoque al principio, pero realmente me gusta ahora, ya que hace que el código fuente sea muy fácil de leer, fácil y divertido de escribir. He aquí un ejemplo:

un tipo de unidad:

public struct Celsius : IEquatable<Celsius> 
{ 
    private readonly Double _value; 
    public const string Abbreviation = "°C"; 

    public Celsius(Double value) 
    { 
     _value = value; 
    } 

    public Boolean Equals(Celsius other) 
    { 
     return _value == other._value; 
    } 

    public override Boolean Equals(Object other) 
    { 
     if (!(other is Celsius)) 
     { 
      return false; 
     } 

     return Equals((Celsius)other); 
    } 

    public override int GetHashCode() 
    { 
     return _value.GetHashCode(); 
    } 

    public override string ToString() 
    { 
     return _value + Abbreviation; 
    } 

    public static explicit operator Celsius(Double value) 
    { 
     return new Celsius(value); 
    } 

    public static explicit operator Double(Celsius value) 
    { 
     return value._value; 
    } 

    public static Boolean operator >(Celsius l, Celsius r) 
    { 
     return l._value > r._value; 
    } 

    public static bool operator <(Celsius l, Celsius r) 
    { 
     return l._value < r._value; 
    } 

    public static Boolean operator >=(Celsius l, Celsius r) 
    { 
     return l._value >= r._value; 
    } 

    public static bool operator <=(Celsius l, Celsius r) 
    { 
     return l._value <= r._value; 
    } 

    public static Boolean operator ==(Celsius l, Celsius r) 
    { 
     return l._value == r._value; 
    } 

    public static bool operator !=(Celsius l, Celsius r) 
    { 
     return l._value != r._value; 
    } 
} 

Unidad extensiones de clase:

public static class UnitsExtensions 
{ 

    public static Celsius Celsius(this Double value) 
    { 
     return new Celsius(value); 
    } 

    public static Celsius Celsius(this Single value) 
    { 
     return new Celsius(value); 
    } 

    public static Celsius Celsius(this Int32 value) 
    { 
     return new Celsius(value); 
    } 

    public static Celsius Celsius(this Decimal value) 
    { 
     return new Celsius((Double)value); 
    } 

    public static Celsius? Celsius(this Decimal? value) 
    { 
     return value == null ? default(Celsius?) : new Celsius((Double)value); 
    } 
} 

Uso:

var temp = (Celsius)value; 

if (temp <= 0.Celsius()) 
{ 
    Console.Writeline("It's cold!"); 
} 
else if (temp < 20.Celsius()) 
{ 
    Console.Writeline("Chilly..."); 
} 
else if (temp < 30.Celsius()) 
{ 
    Console.Writeline("It's quite lovely"); 
} 
else 
{ 
    Console.Writeline("It's hot!"); 
} 

Tengo un número de estos tipos para diferentes medidas, como Millimeter, Radians, Degrees, MillimetersPerSecond, etc. incluso he ido tan lejos como para poner en práctica la división de modo que cuando divido, por ejemplo, por MillimetersPerSecondMillimeters, puedo obtener una TimeSpan valor a cambio. Tal vez esto esté por la borda, pero he encontrado que el tipo de seguridad y la facilidad mental de usar los tipos vale la pena el esfuerzo de implementarlos y mantenerlos.

+4

Ciertamente no vives en Noruega :) 0 == "Es muy bonito", 20 == "¡Está caliente!", 30 == "¡Es insoportable!",> 30 == "¡Sí, claro!" – simendsjo

+2

Entiendo que puede ser muy útil cuando se trabaja con las mismas "unidades"; 10.Celsius() - 10.Fahrenheit(), pero sería bueno poder mezclar unidades como 10.Km()/2.Seconds() y obtener 5 km/s – simendsjo

+1

@simendsjo - De alguna forma hice eso con el MillimetersPerSecond/Milímetros => TimeSpan. Funciona bastante bien. Funcionaría mejor si pudiera asignar un "operador de extensión" a TimeSpan para permitir Millimeters/TimeSpan = MillimetersPerSecond. – codekaizen

1

Agarré esto (con pequeños retoques) de una pregunta anterior de SO. Yo prefiero este estilo, ya que está en línea con los enfoques comunes de DateTime y TimeSpan.

[StructLayout(LayoutKind.Sequential), ComVisible(true)] 
    public struct Distance : IEquatable<Distance>, IComparable<Distance> 
    { 
     private const double MetersPerKilometer = 1000.0; 
     private const double CentimetersPerMeter = 100.0; 
     private const double CentimetersPerInch = 2.54; 
     private const double InchesPerFoot = 12.0; 
     private const double FeetPerYard = 3.0; 
     private const double FeetPerMile = 5280.0; 
     private const double FeetPerMeter = CentimetersPerMeter/(CentimetersPerInch * InchesPerFoot); 
     private const double InchesPerMeter = CentimetersPerMeter/CentimetersPerInch; 

     public static readonly Distance Zero = new Distance(0.0); 

     private readonly double meters; 

     /// <summary> 
     /// Initializes a new Distance to the specified number of meters. 
     /// </summary> 
     /// <param name="meters"></param> 
     public Distance(double meters) 
     { 
      this.meters = meters; 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional kilometers. 
     /// </summary> 
     public double TotalKilometers 
     { 
      get 
      { 
       return meters/MetersPerKilometer; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional meters. 
     /// </summary> 
     public double TotalMeters 
     { 
      get 
      { 
       return meters; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional centimeters. 
     /// </summary> 
     public double TotalCentimeters 
     { 
      get 
      { 
       return meters * CentimetersPerMeter; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional yards. 
     /// </summary> 
     public double TotalYards 
     { 
      get 
      { 
       return meters * FeetPerMeter/FeetPerYard; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional feet. 
     /// </summary> 
     public double TotalFeet 
     { 
      get 
      { 
       return meters * FeetPerMeter; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional inches. 
     /// </summary> 
     public double TotalInches 
     { 
      get 
      { 
       return meters * InchesPerMeter; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional miles. 
     /// </summary> 
     public double TotalMiles 
     { 
      get 
      { 
       return meters * FeetPerMeter/FeetPerMile; 
      } 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of kilometers. 
     /// </summary> 
     /// <param name="value">A number of kilometers.</param> 
     /// <returns></returns> 
     public static Distance FromKilometers(double value) 
     { 
      return new Distance(value * MetersPerKilometer); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of meters. 
     /// </summary> 
     /// <param name="value">A number of meters.</param> 
     /// <returns></returns> 
     public static Distance FromMeters(double value) 
     { 
      return new Distance(value); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of centimeters. 
     /// </summary> 
     /// <param name="value">A number of centimeters.</param> 
     /// <returns></returns> 
     public static Distance FromCentimeters(double value) 
     { 
      return new Distance(value/CentimetersPerMeter); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of yards. 
     /// </summary> 
     /// <param name="value">A number of yards.</param> 
     /// <returns></returns> 
     public static Distance FromYards(double value) 
     { 
      return new Distance(value * FeetPerYard/FeetPerMeter); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of feet. 
     /// </summary> 
     /// <param name="value">A number of feet.</param> 
     /// <returns></returns> 
     public static Distance FromFeet(double value) 
     { 
      return new Distance(value/FeetPerMeter); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of inches. 
     /// </summary> 
     /// <param name="value">A number of inches.</param> 
     /// <returns></returns> 
     public static Distance FromInches(double value) 
     { 
      return new Distance(value/InchesPerMeter); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of miles. 
     /// </summary> 
     /// <param name="value">A number of miles.</param> 
     /// <returns></returns> 
     public static Distance FromMiles(double value) 
     { 
      return new Distance(value * FeetPerMile/FeetPerMeter); 
     } 

     public static bool operator ==(Distance a, Distance b) 
     { 
      return (a.meters == b.meters); 
     } 

     public static bool operator !=(Distance a, Distance b) 
     { 
      return (a.meters != b.meters); 
     } 

     public static bool operator >(Distance a, Distance b) 
     { 
      return (a.meters > b.meters); 
     } 

     public static bool operator >=(Distance a, Distance b) 
     { 
      return (a.meters >= b.meters); 
     } 

     public static bool operator <(Distance a, Distance b) 
     { 
      return (a.meters < b.meters); 
     } 

     public static bool operator <=(Distance a, Distance b) 
     { 
      return (a.meters <= b.meters); 
     } 

     public static Distance operator +(Distance a, Distance b) 
     { 
      return new Distance(a.meters + b.meters); 
     } 

     public static Distance operator -(Distance a, Distance b) 
     { 
      return new Distance(a.meters - b.meters); 
     } 

     public static Distance operator -(Distance a) 
     { 
      return new Distance(-a.meters); 
     } 

     public override bool Equals(object obj) 
     { 
      if (!(obj is Distance)) 
       return false; 

      return Equals((Distance)obj); 
     } 

     public bool Equals(Distance value) 
     { 
      return this.meters == value.meters; 
     } 

     public int CompareTo(Distance value) 
     { 
      return this.meters.CompareTo(value.meters); 
     } 

     public override int GetHashCode() 
     { 
      return meters.GetHashCode(); 
     } 

     public override string ToString() 
     { 
      return string.Format("{0} meters", TotalMeters); 
     } 
    } 
Cuestiones relacionadas