2011-10-21 27 views
6

Tôi đã cố gắng tìm hiểu thêm về đại biểu và lambdas trong khi làm việc trên một dự án nấu ăn nhỏ liên quan đến chuyển đổi nhiệt độ cũng như một số chuyển đổi đo lường nấu ăn như Imperial sang Metric và tôi đã cố gắng nghĩ đến một cách tạo bộ chuyển đổi đơn vị mở rộng.Làm cách nào để tạo bộ chuyển đổi chung cho các đơn vị đo lường trong C#?

Dưới đây là những gì tôi bắt đầu, cùng với nhận xét mã về những gì một số kế hoạch của tôi là. Tôi không có kế hoạch sử dụng nó như dưới đây, tôi đã chỉ thử nghiệm một số tính năng của C# Tôi không biết rất tốt, tôi cũng không chắc chắn làm thế nào để thực hiện điều này thêm. Có ai có bất cứ đề nghị về làm thế nào để tạo ra những gì tôi đang nói về trong các ý kiến ​​dưới đây? Cảm ơn

namespace TemperatureConverter 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      // Fahrenheit to Celsius : [°C] = ([°F] − 32) × 5⁄9 
      var CelsiusResult = Converter.Convert(11M,Converter.FahrenheitToCelsius); 

      // Celsius to Fahrenheit : [°F] = [°C] × 9⁄5 + 32 
      var FahrenheitResult = Converter.Convert(11M, Converter.CelsiusToFahrenheit); 

      Console.WriteLine("Fahrenheit to Celsius : " + CelsiusResult); 
      Console.WriteLine("Celsius to Fahrenheit : " + FahrenheitResult); 
      Console.ReadLine(); 

      // If I wanted to add another unit of temperature i.e. Kelvin 
      // then I would need calculations for Kelvin to Celsius, Celsius to Kelvin, Kelvin to Fahrenheit, Fahrenheit to Kelvin 
      // Celsius to Kelvin : [K] = [°C] + 273.15 
      // Kelvin to Celsius : [°C] = [K] − 273.15 
      // Fahrenheit to Kelvin : [K] = ([°F] + 459.67) × 5⁄9 
      // Kelvin to Fahrenheit : [°F] = [K] × 9⁄5 − 459.67 
      // The plan is to have the converters with a single purpose to convert to 
      //one particular unit type e.g. Celsius and create separate unit converters 
      //that contain a list of calculations that take one specified unit type and then convert to their particular unit type, in this example its Celsius. 
     } 
    } 

    // at the moment this is a static class but I am looking to turn this into an interface or abstract class 
    // so that whatever implements this interface would be supplied with a list of generic deligate conversions 
    // that it can invoke and you can extend by adding more when required. 
    public static class Converter 
    { 
     public static Func<decimal, decimal> CelsiusToFahrenheit = x => (x * (9M/5M)) + 32M; 
     public static Func<decimal, decimal> FahrenheitToCelsius = x => (x - 32M) * (5M/9M); 

     public static decimal Convert(decimal valueToConvert, Func<decimal, decimal> conversion) { 
      return conversion.Invoke(valueToConvert); 
     } 
    } 
} 

Cập nhật: Đang cố gắng để làm rõ câu hỏi của tôi:

Sử dụng chỉ là ví dụ nhiệt độ của tôi dưới đây, thế nào tôi sẽ tạo ra một lớp có chứa một danh sách chuyển đổi lambda sang Celsius mà bạn sau đó vượt qua nó một cho nhiệt độ và nó sẽ cố gắng và chuyển đổi đó sang Celsius (nếu tính toán có sẵn)

Ví dụ mã giả:

enum Temperature 
{ 
    Celcius, 
    Fahrenheit, 
    Kelvin 
} 

UnitConverter CelsiusConverter = new UnitConverter(Temperature.Celsius); 
CelsiusConverter.AddCalc("FahrenheitToCelsius", lambda here); 
CelsiusConverter.Convert(Temperature.Fahrenheit, 11); 
+0

Đơn vị đo được hỗ trợ trong F # và tôi nghĩ rằng nó có thể là một [Tính năng] tốt (https://github.com/dotnet/roslyn/issues/144) cho C# vNext. Bây giờ tôi đã tìm thấy dự án này [QuantityTypes] (https://github.com/objorke/QuantityTypes) để thực hiện các đơn vị đo lường trong C#. – orad

Trả lời

21

Tôi nghĩ đây là một vấn đề nhỏ thú vị, vì vậy tôi đã quyết định xem mức độ độc đáo này có thể được bao bọc thành một triển khai chung.Điều này không được kiểm tra tốt (và không xử lý tất cả các trường hợp lỗi - chẳng hạn như nếu bạn không đăng ký chuyển đổi cho một loại đơn vị cụ thể, sau đó vượt qua điều đó), nhưng nó có thể hữu ích. Trọng tâm là làm cho lớp kế thừa (TemperatureConverter) càng gọn gàng càng tốt.

/// <summary> 
/// Generic conversion class for converting between values of different units. 
/// </summary> 
/// <typeparam name="TUnitType">The type representing the unit type (eg. enum)</typeparam> 
/// <typeparam name="TValueType">The type of value for this unit (float, decimal, int, etc.)</typeparam> 
abstract class UnitConverter<TUnitType, TValueType> 
{ 
    /// <summary> 
    /// The base unit, which all calculations will be expressed in terms of. 
    /// </summary> 
    protected static TUnitType BaseUnit; 

    /// <summary> 
    /// Dictionary of functions to convert from the base unit type into a specific type. 
    /// </summary> 
    static ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>> ConversionsTo = new ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>>(); 

    /// <summary> 
    /// Dictionary of functions to convert from the specified type into the base unit type. 
    /// </summary> 
    static ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>> ConversionsFrom = new ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>>(); 

    /// <summary> 
    /// Converts a value from one unit type to another. 
    /// </summary> 
    /// <param name="value">The value to convert.</param> 
    /// <param name="from">The unit type the provided value is in.</param> 
    /// <param name="to">The unit type to convert the value to.</param> 
    /// <returns>The converted value.</returns> 
    public TValueType Convert(TValueType value, TUnitType from, TUnitType to) 
    { 
     // If both From/To are the same, don't do any work. 
     if (from.Equals(to)) 
      return value; 

     // Convert into the base unit, if required. 
     var valueInBaseUnit = from.Equals(BaseUnit) 
           ? value 
           : ConversionsFrom[from](value); 

     // Convert from the base unit into the requested unit, if required 
     var valueInRequiredUnit = to.Equals(BaseUnit) 
           ? valueInBaseUnit 
           : ConversionsTo[to](valueInBaseUnit); 

     return valueInRequiredUnit; 
    } 

    /// <summary> 
    /// Registers functions for converting to/from a unit. 
    /// </summary> 
    /// <param name="convertToUnit">The type of unit to convert to/from, from the base unit.</param> 
    /// <param name="conversionTo">A function to convert from the base unit.</param> 
    /// <param name="conversionFrom">A function to convert to the base unit.</param> 
    protected static void RegisterConversion(TUnitType convertToUnit, Func<TValueType, TValueType> conversionTo, Func<TValueType, TValueType> conversionFrom) 
    { 
     if (!ConversionsTo.TryAdd(convertToUnit, conversionTo)) 
      throw new ArgumentException("Already exists", "convertToUnit"); 
     if (!ConversionsFrom.TryAdd(convertToUnit, conversionFrom)) 
      throw new ArgumentException("Already exists", "convertToUnit"); 
    } 
} 

Loại chung cho một đơn vị đại diện cho đơn vị và loại cho giá trị. Để sử dụng nó, bạn chỉ cần thừa kế từ lớp này (cung cấp các kiểu) và đăng ký một số lambdas để thực hiện chuyển đổi. Dưới đây là một ví dụ cho nhiệt độ (với một số tính toán dummy):

enum Temperature 
{ 
    Celcius, 
    Fahrenheit, 
    Kelvin 
} 

class TemperatureConverter : UnitConverter<Temperature, float> 
{ 
    static TemperatureConverter() 
    { 
     BaseUnit = Temperature.Celcius; 
     RegisterConversion(Temperature.Fahrenheit, v => v * 2f, v => v * 0.5f); 
     RegisterConversion(Temperature.Kelvin, v => v * 10f, v => v * 0.05f); 
    } 
} 

Và sau đó sử dụng nó là khá đơn giản:

var converter = new TemperatureConverter(); 

Console.WriteLine(converter.Convert(1, Temperature.Celcius, Temperature.Fahrenheit)); 
Console.WriteLine(converter.Convert(1, Temperature.Fahrenheit, Temperature.Celcius)); 

Console.WriteLine(converter.Convert(1, Temperature.Celcius, Temperature.Kelvin)); 
Console.WriteLine(converter.Convert(1, Temperature.Kelvin, Temperature.Celcius)); 

Console.WriteLine(converter.Convert(1, Temperature.Kelvin, Temperature.Fahrenheit)); 
Console.WriteLine(converter.Convert(1, Temperature.Fahrenheit, Temperature.Kelvin)); 
+0

Chính xác những gì tôi đã sau, câu trả lời tuyệt vời :) rất nhiều để đọc qua và hiểu đầy đủ cho cả hai nhưng cảm ơn rất nhiều cho sự giúp đỡ. – Pricey

3

Có vẻ như bạn muốn một cái gì đó như:

Func<decimal, decimal> celsiusToKelvin = x => x + 273.15m; 
Func<decimal, decimal> kelvinToCelsius = x => x - 273.15m; 
Func<decimal, decimal> fahrenheitToKelvin = x => ((x + 459.67m) * 5m)/9m; 
Func<decimal, decimal> kelvinToFahrenheit = x => ((x * 9m)/5m) - 459.67m; 

Tuy nhiên, bạn có thể muốn xem xét không chỉ sử dụng decimal, nhưng có một loại mà biết các đơn vị để bạn có thể không vô tình (nói) áp dụng "Celsius to Kelvin" chuyển đổi thành giá trị không phải độ C. Có thể có một cái nhìn tại cách tiếp cận F# Units of Measure cho cảm hứng.

+0

Yea Tôi đang cố gắng tạo ra một lớp để thực hiện giao diện Chuyển đổi Đơn vị và lớp đó chỉ biết cách chuyển đổi danh sách các loại nhiệt độ thành Celsius nhưng sau đó tôi có thể tạo một lớp hoàn toàn mới thực hiện giao diện chuyển đổi đơn vị và biết cách chuyển đổi nói một phép đo cốc thành các bảng hoặc đế quốc thành số liệu, v.v .. tôi sẽ thử chỉnh sửa câu hỏi của mình để giải thích thêm về bản thân mình. Cảm ơn một lần nữa cho các liên kết đơn vị F # đơn vị đo lường :) – Pricey

+0

xin lỗi đế quốc để số liệu là một ví dụ xấu trong bình luận ở trên của tôi, một ví dụ tốt hơn sẽ được £ đến gram, đó là cụ thể hơn. – Pricey

5

Bạn có một khởi đầu tốt, nhưng như Jon đã nói, hiện tại nó không an toàn; bộ chuyển đổi không có kiểm tra lỗi để đảm bảo số thập phân mà nó nhận được là giá trị Celsius.

Vì vậy, để thực hiện điều này hơn nữa, tôi sẽ bắt đầu giới thiệu các loại cấu trúc có giá trị bằng số và áp dụng nó cho một đơn vị đo lường. Trong các mẫu Kiến trúc doanh nghiệp (còn gọi là mẫu thiết kế Gang of Four), mẫu này được gọi là mẫu "Tiền" sau khi sử dụng phổ biến nhất, để biểu thị số lượng loại tiền tệ. Mẫu giữ cho bất kỳ số lượng nào yêu cầu đơn vị đo lường có ý nghĩa.

Ví dụ:

public enum TemperatureScale 
{ 
    Celsius, 
    Fahrenheit, 
    Kelvin 
} 

public struct Temperature 
{ 
    decimal Degrees {get; private set;} 
    TemperatureScale Scale {get; private set;} 

    public Temperature(decimal degrees, TemperatureScale scale) 
    { 
     Degrees = degrees; 
     Scale = scale; 
    } 

    public Temperature(Temperature toCopy) 
    { 
     Degrees = toCopy.Degrees; 
     Scale = toCopy.Scale; 
    } 
} 

Bây giờ, bạn có một loại đơn giản mà bạn có thể sử dụng để thực thi là chuyển đổi bạn đang làm mất một nhiệt độ đó là quy mô thích hợp, và trả về một Nhiệt độ kết quả được biết đến là ở quy mô khác.

Funcs của bạn sẽ cần thêm dòng để kiểm tra xem đầu vào có khớp với đầu ra hay không; bạn có thể tiếp tục sử dụng lambdas, hoặc bạn có thể mang nó thêm một bước nữa với một mô hình chiến lược đơn giản:

public interface ITemperatureConverter 
{ 
    public Temperature Convert(Temperature input); 
} 

public class FahrenheitToCelsius:ITemperatureConverter 
{ 
    public Temperature Convert(Temperature input) 
    { 
     if (input.Scale != TemperatureScale.Fahrenheit) 
     throw new ArgumentException("Input scale is not Fahrenheit"); 

     return new Temperature(input.Degrees * 5m/9m - 32, TemperatureScale.Celsius); 
    } 
} 

//Implement other conversion methods as ITemperatureConverters 

public class TemperatureConverter 
{ 
    public Dictionary<Tuple<TemperatureScale, TemperatureScale>, ITemperatureConverter> converters = 
     new Dictionary<Tuple<TemperatureScale, TemperatureScale>, ITemperatureConverter> 
     { 
     {Tuple.Create<TemperatureScale.Fahrenheit, TemperatureScale.Celcius>, 
      new FahrenheitToCelsius()}, 
     {Tuple.Create<TemperatureScale.Celsius, TemperatureScale.Fahrenheit>, 
      new CelsiusToFahrenheit()}, 
     ... 
     } 

    public Temperature Convert(Temperature input, TemperatureScale toScale) 
    { 
     if(!converters.ContainsKey(Tuple.Create(input.Scale, toScale)) 
     throw new InvalidOperationException("No converter available for this conversion"); 

     return converters[Tuple.Create(input.Scale, toScale)].Convert(input); 
    } 
} 

Bởi vì các loại chuyển đổi hai chiều, bạn có thể xem xét thiết lập giao diện để xử lý cả hai cách, với phương thức "ConvertBack" hoặc tương tự sẽ lấy Nhiệt độ theo thang đo Celsius và chuyển sang Fahrenheit. Điều đó làm giảm số lượng lớp học của bạn. Sau đó, thay vì các cá thể lớp, các giá trị từ điển của bạn có thể là các con trỏ tới các phương thức trên các cá thể của các trình biến đổi. Điều này làm tăng độ phức tạp của việc thiết lập bộ chọn chiến lược TemperatureConverter chính, nhưng làm giảm số lượng các lớp chiến lược chuyển đổi mà bạn phải xác định.Cũng cần lưu ý rằng việc kiểm tra lỗi được thực hiện khi chạy trên thực tế khi bạn thực sự cố gắng thực hiện chuyển đổi, yêu cầu mã này phải được kiểm tra kỹ trong tất cả các tập quán để đảm bảo mã luôn đúng. Để tránh điều này, bạn có thể lấy được lớp Nhiệt độ cơ bản để tạo ra các cấu trúc CelsiusTemperature và FahrenheitTemperature, mà chỉ đơn giản là xác định Thang đo của chúng như là một giá trị không đổi. Sau đó, ITemperatureConverter có thể được tạo thành chung cho hai loại, cả hai Nhiệt độ, cho phép bạn kiểm tra thời gian biên dịch mà bạn đang xác định chuyển đổi mà bạn nghĩ bạn đang có. các TemperatureConverter cũng có thể được thực hiện để tự động tìm ITemperatureConverters, xác định các loại họ sẽ chuyển đổi giữa, và automagically thiết lập từ điển chuyển đổi, do đó bạn không bao giờ phải lo lắng về việc thêm những cái mới. Điều này xảy ra với chi phí tăng số lượng lớp học dựa trên Nhiệt độ; bạn sẽ cần bốn lớp miền (một cơ sở và ba lớp dẫn xuất) thay vì một. Nó cũng sẽ làm chậm việc tạo ra một lớp TemperatureConverter như mã để xây dựng một cách phản ánh từ điển chuyển đổi sẽ sử dụng khá nhiều sự phản ánh.

Bạn cũng có thể thay đổi enums cho các đơn vị đo lường để trở thành "lớp đánh dấu"; các lớp trống không có ý nghĩa nào khác hơn là chúng thuộc lớp đó và xuất phát từ các lớp khác. Sau đó bạn có thể định nghĩa một hệ thống phân cấp đầy đủ của các lớp "UnitOfMeasure" đại diện cho các đơn vị đo lường khác nhau và có thể được sử dụng như các đối số và ràng buộc kiểu chung chung; ITemperatureConverter có thể là chung cho hai loại, cả hai loại này đều bị ràng buộc là các lớp TemperatureScale và việc triển khai CelsiusFahrenheitConverter sẽ đóng giao diện chung cho các loại CelsiusDegrees và FahrenheitDegrees đều có nguồn gốc từ TemperatureScale. Điều đó cho phép bạn vạch trần các đơn vị đo lường như là các ràng buộc của một chuyển đổi, lần lượt cho phép chuyển đổi giữa các loại đơn vị đo lường (một số đơn vị vật liệu nhất định đã biết chuyển đổi; 1 Pint Imperial nước nặng 1,25 pound).

Tất cả những quyết định thiết kế này sẽ đơn giản hóa một loại thay đổi đối với thiết kế này, nhưng với một số chi phí (hoặc làm điều gì đó khó khăn hơn để thực hiện hoặc giảm hiệu suất thuật toán). Việc quyết định điều gì thực sự "dễ dàng" đối với bạn, trong bối cảnh của ứng dụng tổng thể và môi trường mã hóa bạn làm việc.

CHỈNH SỬA: Cách sử dụng bạn muốn, rất dễ dàng với nhiệt độ . Tuy nhiên, nếu bạn muốn một UnitConverter chung có thể làm việc với bất kỳ UnitofMeasure nào, thì bạn không còn muốn Enums đại diện cho các đơn vị đo lường của bạn nữa, bởi vì Enums không thể có một hệ thống phân cấp thừa kế tùy chỉnh (chúng lấy trực tiếp từ System.Enum).

Bạn có thể chỉ định hàm tạo mặc định có thể chấp nhận bất kỳ Enum nào, nhưng bạn phải đảm bảo rằng Enum là một trong các loại đơn vị đo lường, nếu không bạn có thể chuyển giá trị DialogResult và trình biến đổi trong thời gian chạy. Thay vào đó, nếu bạn muốn một UnitConverter có thể chuyển đổi sang bất kỳ UnitOfMeasure nào cho lambdas cho các đơn vị đo lường khác, tôi sẽ chỉ định đơn vị đo lường là "các lớp đánh dấu"; nhỏ stateless "thẻ" mà chỉ có ý nghĩa ở chỗ chúng là kiểu riêng của họ và lấy được từ các bậc phụ huynh:

//The only functionality any UnitOfMeasure needs is to be semantically equatable 
//with any other reference to the same type. 
public abstract class UnitOfMeasure:IEquatable<UnitOfMeasure> 
{ 
    public override bool Equals(UnitOfMeasure other) 
    { 
     return this.ReferenceEquals(other) 
     || this.GetType().Name == other.GetType().Name; 
    } 

    public override bool Equals(Object other) 
    { 
     return other is UnitOfMeasure && this.Equals(other as UnitOfMeasure); 
    }  

    public override operator ==(Object other) {return this.Equals(other);} 
    public override operator !=(Object other) {return this.Equals(other) == false;} 

} 

public abstract class Temperature:UnitOfMeasure { 
public static CelsiusTemperature Celsius {get{return new CelsiusTemperature();}} 
public static FahrenheitTemperature Fahrenheit {get{return new CelsiusTemperature();}} 
public static KelvinTemperature Kelvin {get{return new CelsiusTemperature();}} 
} 
public class CelsiusTemperature:Temperature{} 
public class FahrenheitTemperature :Temperature{} 
public class KelvinTemperature :Temperature{} 

... 

public class UnitConverter 
{ 
    public UnitOfMeasure BaseUnit {get; private set;} 
    public UnitConverter(UnitOfMeasure baseUnit) {BaseUnit = baseUnit;} 

    private readonly Dictionary<UnitOfMeasure, Func<decimal, decimal>> converters 
     = new Dictionary<UnitOfMeasure, Func<decimal, decimal>>(); 

    public void AddConverter(UnitOfMeasure measure, Func<decimal, decimal> conversion) 
    { converters.Add(measure, conversion); } 

    public void Convert(UnitOfMeasure measure, decimal input) 
    { return converters[measure](input); } 
} 

Bạn có thể đưa vào kiểm tra lỗi (kiểm tra xem các đơn vị đầu vào có một sự chuyển đổi quy định, kiểm tra xem một chuyển đổi đang được thêm vào là cho một UOM có cùng một kiểu gốc như kiểu cơ sở, vv ..) khi bạn thấy phù hợp. Bạn cũng có thể lấy được UnitConverter để tạo ra TemperatureConverter, cho phép bạn thêm các kiểm tra kiểu tĩnh, biên dịch-thời gian và tránh các kiểm tra thời gian chạy mà UnitConverter sẽ phải sử dụng.

+0

+1 cho phản ứng tuyệt vời và bình luận của bạn thực sự giúp đỡ với điểm tôi không xem xét – Pricey

0

Bình thường, tôi muốn để thêm video này như một bình luận cho bài Danny Tuppeny, nhưng nó có vẻ như tôi không thể thêm điều này làm bình luận.

Tôi đã cải thiện giải pháp từ @Danny Tuppeny một chút. Tôi không muốn thêm từng chuyển đổi với hai yếu tố hội thoại, bởi vì chỉ có một yếu tố cần thiết. Ngoài ra các tham số của loại Func không có vẻ là cần thiết, nó chỉ làm cho nó phức tạp hơn cho người dùng.

Vì vậy, cuộc gọi của tôi sẽ trông giống như:

public enum TimeUnit 
{ 
    Milliseconds, 
    Second, 
    Minute, 
    Hour, 
    Day, 
    Week 
} 

public class TimeConverter : UnitConverter<TimeUnit, double> 
{ 
    static TimeConverter() 
    { 
     BaseUnit = TimeUnit.Second; 
     RegisterConversion(TimeUnit.Milliseconds, 1000); 
     RegisterConversion(TimeUnit.Minute, 1/60); 
     RegisterConversion(TimeUnit.Hour, 1/3600); 
     RegisterConversion(TimeUnit.Day, 1/86400); 
     RegisterConversion(TimeUnit.Week, 1/604800); 
    } 
} 

Tôi cũng đã thêm một phương pháp để có được những yếu tố chuyển đổi giữa các đơn vị. Đây là lớp UnitConverter chỉnh sửa:

/// <summary> 
/// Generic conversion class for converting between values of different units. 
/// </summary> 
/// <typeparam name="TUnitType">The type representing the unit type (eg. enum)</typeparam> 
/// <typeparam name="TValueType">The type of value for this unit (float, decimal, int, etc.)</typeparam> 
/// <remarks>http://stackoverflow.com/questions/7851448/how-do-i-create-a-generic-converter-for-units-of-measurement-in-c 
/// </remarks> 
public abstract class UnitConverter<TUnitType, TValueType> where TValueType : struct, IComparable, IComparable<TValueType>, IEquatable<TValueType>, IConvertible 
{ 
    /// <summary> 
    /// The base unit, which all calculations will be expressed in terms of. 
    /// </summary> 
    protected static TUnitType BaseUnit; 

    /// <summary> 
    /// Dictionary of functions to convert from the base unit type into a specific type. 
    /// </summary> 
    static ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>> ConversionsTo = new ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>>(); 

    /// <summary> 
    /// Dictionary of functions to convert from the specified type into the base unit type. 
    /// </summary> 
    static ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>> ConversionsFrom = new ConcurrentDictionary<TUnitType, Func<TValueType, TValueType>>(); 

    /// <summary> 
    /// Converts a value from one unit type to another. 
    /// </summary> 
    /// <param name="value">The value to convert.</param> 
    /// <param name="from">The unit type the provided value is in.</param> 
    /// <param name="to">The unit type to convert the value to.</param> 
    /// <returns>The converted value.</returns> 
    public TValueType Convert(TValueType value, TUnitType from, TUnitType to) 
    { 
     // If both From/To are the same, don't do any work. 
     if (from.Equals(to)) 
      return value; 

     // Convert into the base unit, if required. 
     var valueInBaseUnit = from.Equals(BaseUnit) 
           ? value 
           : ConversionsFrom[from](value); 

     // Convert from the base unit into the requested unit, if required 
     var valueInRequiredUnit = to.Equals(BaseUnit) 
           ? valueInBaseUnit 
           : ConversionsTo[to](valueInBaseUnit); 

     return valueInRequiredUnit; 
    } 

    public double ConversionFactor(TUnitType from, TUnitType to) 
    { 
     return Convert(One(), from, to).ToDouble(CultureInfo.InvariantCulture); 
    } 

    /// <summary> 
    /// Registers functions for converting to/from a unit. 
    /// </summary> 
    /// <param name="convertToUnit">The type of unit to convert to/from, from the base unit.</param> 
    /// <param name="conversionToFactor">a factor converting into the base unit.</param> 
    protected static void RegisterConversion(TUnitType convertToUnit, TValueType conversionToFactor) 
    { 
     if (!ConversionsTo.TryAdd(convertToUnit, v=> Multiply(v, conversionToFactor))) 
      throw new ArgumentException("Already exists", "convertToUnit"); 

     if (!ConversionsFrom.TryAdd(convertToUnit, v => MultiplicativeInverse(conversionToFactor))) 
      throw new ArgumentException("Already exists", "convertToUnit"); 
    } 

    static TValueType Multiply(TValueType a, TValueType b) 
    { 
     // declare the parameters 
     ParameterExpression paramA = Expression.Parameter(typeof(TValueType), "a"); 
     ParameterExpression paramB = Expression.Parameter(typeof(TValueType), "b"); 
     // add the parameters together 
     BinaryExpression body = Expression.Multiply(paramA, paramB); 
     // compile it 
     Func<TValueType, TValueType, TValueType> multiply = Expression.Lambda<Func<TValueType, TValueType, TValueType>>(body, paramA, paramB).Compile(); 
     // call it 
     return multiply(a, b); 
    } 

    static TValueType MultiplicativeInverse(TValueType b) 
    { 
     // declare the parameters 
     ParameterExpression paramA = Expression.Parameter(typeof(TValueType), "a"); 
     ParameterExpression paramB = Expression.Parameter(typeof(TValueType), "b"); 
     // add the parameters together 
     BinaryExpression body = Expression.Divide(paramA, paramB); 
     // compile it 
     Func<TValueType, TValueType, TValueType> divide = Expression.Lambda<Func<TValueType, TValueType, TValueType>>(body, paramA, paramB).Compile(); 
     // call it 
     return divide(One(), b); 
    } 

    //Returns the value "1" as converted Type 
    static TValueType One() 
    { 
     return (TValueType) System.Convert.ChangeType(1, typeof (TValueType)); 
    } 
} 
0

Người ta có thể định nghĩa một đơn vị vật lý chung kiểu như vậy mà, nếu một người có cho mỗi đơn vị một loại mà thực hiện new và bao gồm một phương pháp dịch giữa các đơn vị và một "đơn vị cơ sở "của loại đó, người ta có thể thực hiện số học trên các giá trị được biểu thị bằng các đơn vị khác nhau và chuyển đổi chúng khi cần, sử dụng hệ thống kiểu sao cho một biến loại AreaUnit<LengthUnit.Inches> sẽ chỉ chấp nhận những thứ được tính theo inch vuông, nhưng nếu một giá trị là myAreaInSquareInches= AreaUnit<LengthUnit.Inches>.Product(someLengthInCentimeters, someLengthInFathoms); sẽ tự động dịch những đơn vị khác trước khi thực hiện phép nhân. Nó thực sự có thể hoạt động khá tốt khi sử dụng cú pháp phương thức gọi vì các phương thức như phương thức Product<T1,T2>(T1 p1, T2 p2) có thể chấp nhận các tham số kiểu generic của toán hạng. Thật không may, không có cách nào để làm cho các toán tử chung chung, cũng như không có cách nào cho một kiểu như AreaUnit<T> where T:LengthUnitDescriptor để xác định một phương tiện chuyển đổi đến hoặc từ một số kiểu tùy ý chung khác AreaUnit<U>. An AreaUnit<T> có thể xác định chuyển đổi đến và từ ví dụ: AreaUnit<Angstrom>, nhưng không có cách nào trình biên dịch có thể được thông báo rằng mã được cấp cho một khu vực AreaUnit<Centimeters> and wants AreaUnit` có thể chuyển đổi inch thành angstrom và sau đó thành cm.

1

Bạn có thể xem Unit.NET. Đó là trên GitHubNuGet. Nó cung cấp các đơn vị và chuyển đổi phổ biến nhất, hỗ trợ cả việc gõ tĩnh và liệt kê các đơn vị và phân tích/in các chữ viết tắt. Mặc dù nó không phân tích các biểu thức, và bạn không thể mở rộng các lớp đơn vị hiện có, nhưng bạn có thể mở rộng nó với các đơn vị của bên thứ ba mới.

Ví dụ chuyển đổi:

Length meter = Length.FromMeters(1); 
double cm = meter.Centimeters; // 100 
double feet = meter.Feet; // 3.28084 
Các vấn đề liên quan