2011-10-21 38 views
6

मैं एक छोटे से खाना पकाने की परियोजना पर काम करते हुए प्रतिनिधियों और भेड़ के बच्चे के बारे में कुछ और सीखने की कोशिश कर रहा हूं जिसमें तापमान रूपांतरण शामिल है और कुछ खाना पकाने के माप रूपांतरण जैसे इंपीरियल टू मेट्रिक और मैं एक तरीका सोचने की कोशिश कर रहा हूं एक एक्स्टेंसिबल यूनिट कनवर्टर बनाओ।मैं सी # में माप की इकाइयों के लिए एक सामान्य कनवर्टर कैसे बना सकता हूं?

यहां मेरी कुछ योजनाओं के बारे में कोड टिप्पणियों के साथ मैंने शुरुआत की है। मेरे पास नीचे की तरह इसका उपयोग करने की कोई योजना नहीं है, मैं सिर्फ सी # की कुछ विशेषताओं का परीक्षण कर रहा था, मुझे बहुत अच्छी तरह से पता नहीं है, मैं यह भी सुनिश्चित नहीं कर रहा हूं कि इसे और कैसे लेना है। क्या किसी के पास नीचे दी गई टिप्पणियों के बारे में बात करने के बारे में कोई सुझाव है? धन्यवाद

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); 
     } 
    } 
} 

अद्यतन: मेरे सवाल का स्पष्ट करने के लिए कोशिश कर रहा है:

नीचे बस अपना तापमान उदाहरण का उपयोग, मैं कैसे एक वर्ग है कि सेल्सियस तक लैम्ब्डा रूपांतरण की एक सूची है बनाना होगा आप तो यह एक पारित जो निश्चित तापमान और यह कोशिश करते हैं और सेल्सियस तक कि परिवर्तित (यदि गणना उपलब्ध है)

उदाहरण छद्म कोड होगा:

enum Temperature 
{ 
    Celcius, 
    Fahrenheit, 
    Kelvin 
} 

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

मापक इकाई एफ # में समर्थन कर रहे हैं और मैं सी # के लिए यह एक अच्छा [सुविधा] हो सकता है लगता है (https://github.com/dotnet/roslyn/issues/144) vNext। अभी के लिए मुझे यह प्रोजेक्ट [क्वांटिटी टाइप्स] (https://github.com/objorke/QuantityTypes) मिला है जो सी # में माप की इकाइयों को लागू करता है। – orad

उत्तर

21

मैंने सोचा कि यह एक दिलचस्प छोटी समस्या थी, इसलिए मैंने यह देखने का फैसला किया कि यह एक सामान्य कार्यान्वयन में कितनी अच्छी तरह से लपेटा जा सकता है।यह अच्छी तरह से परीक्षण नहीं किया जाता है (और सभी त्रुटि मामलों को संभाल नहीं करता है - जैसे कि यदि आप किसी विशेष इकाई प्रकार के लिए रूपांतरण पंजीकृत नहीं करते हैं, तो उसमें पास करें), लेकिन यह उपयोगी हो सकता है। विरासत विरासत वर्ग (TemperatureConverter) जितना संभव हो उतना साफ करने पर था।

/// <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"); 
    } 
} 

सामान्य प्रकार के तर्क एक enum के लिए हैं जो इकाइयों का प्रतिनिधित्व करता है, और मूल्य के प्रकार के लिए। इसका उपयोग करने के लिए, आपको बस इस वर्ग (प्रकार प्रदान करने) से उत्तराधिकारी होना होगा और रूपांतरण करने के लिए कुछ भेड़-बकरियों को पंजीकृत करना होगा। है बहुत सरल

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); 
    } 
} 

और फिर इसे का उपयोग:: यहाँ (कुछ डमी गणना के साथ) तापमान के लिए एक उदाहरण है

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

पर विचार नहीं किया था, वास्तव में मैं क्या कर रहा था, बहुत अच्छा जवाब :) बहुत सारे पढ़ने और समझने के लिए बहुत कुछ धन्यवाद लेकिन मदद के लिए बहुत धन्यवाद। – Pricey

3

यह आप की तरह लगता है जैसे कुछ हैं:

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; 

हालांकि, अगर आप सिर्फ decimal का उपयोग नहीं है, लेकिन एक प्रकार है जो इकाइयों जानता रखने पर विचार करना चाह सकते हैं ताकि आप नहीं गलती से (माना) लागू कर सकते हैं "सेल्सियस से केल्विन" एक गैर-सेल्सियस मूल्य में रूपांतरण। शायद प्रेरणा के लिए F# Units of Measure दृष्टिकोण पर एक नज़र डालें।

+0

हाँ, मैं इसे बनाने की कोशिश कर रहा हूं ताकि मैं संभावित रूप से एक वर्ग हो सकूं जो यूनिट कन्वर्टर इंटरफेस लागू करता है और उस विशेष श्रेणी को केवल तापमान प्रकारों की सूची को सेल्सियस में परिवर्तित करने के बारे में पता होगा, लेकिन फिर मैं एक पूरी नई कक्षा बना सकता हूं यूनिट कनवर्टर इंटरफ़ेस को लागू करता है और जानता है कि कैसे एक कप माप को चम्मच या मीट्रिक के लिए शाही आदि में कनवर्ट करना है .. मैं अपने प्रश्न को एक और संपादित करने की कोशिश करूंगा। मापन लिंक के एफ # इकाइयों के लिए फिर से धन्यवाद :) – Pricey

+0

मीट्रिक के लिए माफी माफ करना मेरी उपर्युक्त टिप्पणी में एक बुरा उदाहरण है, बेहतर एक ग्राम के लिए पाउंड होगा, जो अधिक विशिष्ट है। – Pricey

5

आपके पास अच्छी शुरुआत है, लेकिन जैसे जॉन ने कहा, यह वर्तमान में टाइप-सुरक्षित नहीं है; कनवर्टर को यह सुनिश्चित करने के लिए कोई त्रुटि-जांच नहीं है कि यह दशमलव सेल्सियस मान है।

तो, इसे आगे बढ़ाने के लिए, मैं संरचनात्मक प्रकारों को शुरू करना शुरू करूंगा जो संख्यात्मक मान लेते हैं और इसे माप की इकाई पर लागू करते हैं। एंटरप्राइज़ आर्किटेक्चर के पैटर्न (उर्फ चार डिजाइन पैटर्न की गिरोह) में, इसे सबसे आम उपयोग के बाद "मनी" पैटर्न कहा जाता है, जो कि एक प्रकार की मुद्रा को दर्शाता है। पैटर्न किसी भी संख्यात्मक राशि के लिए रखता है जिसके लिए माप की इकाई को सार्थक होना आवश्यक है।

उदाहरण:

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; 
    } 
} 

अब, आप एक सरल प्रकार है कि आप एक परिणाम के रूप में जाने जाते तापमान लौट लागू करने के लिए है कि रूपांतरण आप कर रहे हैं एक तापमान उचित पैमाने की है कि लेने के लिए उपयोग कर सकते हैं, और राशि दूसरे पैमाने पर।

इनपुट के मिलान से जांचने के लिए आपके Funcs को एक अतिरिक्त पंक्ति की आवश्यकता होगी; आप lambdas उपयोग करने के लिए जारी रख सकते हैं, या आप इसे एक सरल रणनीति पैटर्न के साथ एक कदम आगे ले जा सकते हैं:

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); 
    } 
} 

क्योंकि रूपांतरण के इन प्रकार के दो तरह से कर रहे हैं, तो आप दोनों तरीकों को संभालने के लिए इंटरफ़ेस की स्थापना पर विचार कर सकते, "कन्वर्टबैक" विधि या इसी तरह के साथ जो सेल्सियस स्केल में तापमान लेगा और फारेनहाइट में परिवर्तित होगा। इससे आपकी कक्षा गिनती कम हो जाती है। फिर, कक्षा के उदाहरणों के बजाय, आपके शब्दकोश मूल्य कन्वर्टर्स के उदाहरणों पर विधियों के लिए पॉइंटर्स हो सकते हैं। यह मुख्य तापमान कनवर्टर रणनीति-पिकर को स्थापित करने की जटिलता को बढ़ाता है, लेकिन रूपांतरण रणनीति वर्गों की संख्या को कम करता है जिसे आपको परिभाषित करना होगा।

यह भी ध्यान दें कि त्रुटि-जांच रनटाइम पर की जाती है जब आप वास्तव में रूपांतरण करने की कोशिश कर रहे होते हैं, यह सुनिश्चित करने के लिए कि यह हमेशा सही है, इस कोड को सभी उपयोगों में पूरी तरह से परीक्षण किया जाना आवश्यक है। इससे बचने के लिए, आप सेल्सियस तापमान और फारेनहाइट तापमान तापमान का उत्पादन करने के लिए बेस तापमान वर्ग प्राप्त कर सकते हैं, जो कि उनके स्केल को निरंतर मूल्य के रूप में परिभाषित करेगा। फिर, ITEMperatureConverter को दो प्रकार के दोनों प्रकार के सामान्य बना दिया जा सकता है, दोनों तापमान, आपको संकलन-समय की जांच कर रहा है कि आप उस रूपांतरण को निर्दिष्ट कर रहे हैं जो आप सोचते हैं कि आप हैं। तापमान कनवर्टर को गतिशील रूप से ITemperatureConverters खोजने के लिए भी बनाया जा सकता है, वे किस प्रकार के रूप में परिवर्तित होंगे, और स्वचालित रूप से कन्वर्टर्स के शब्दकोश को सेट अप करने के लिए निर्धारित किए जा सकते हैं ताकि आपको नए जोड़ने के बारे में चिंता न करें। यह तापमान-आधारित वर्ग गणना में वृद्धि की लागत पर आता है; आपको एक के बजाय चार डोमेन कक्षाएं (आधार और तीन व्युत्पन्न कक्षाएं) की आवश्यकता होगी। यह तापमान कनवर्टर क्लास के निर्माण को भी धीमा कर देगा क्योंकि कनवर्टर डिक्शनरी को प्रतिबिंबित करने के लिए कोड काफी प्रतिबिंब का उपयोग करेगा।

आप "मार्कर कक्षाएं" बनने के लिए माप की इकाइयों के लिए enums भी बदल सकते हैं; खाली वर्ग जिनके अलावा कोई अर्थ नहीं है, वे उस वर्ग के हैं और अन्य वर्गों से प्राप्त होते हैं। इसके बाद आप "यूनिटऑफमेजर" कक्षाओं का एक पूर्ण पदानुक्रम परिभाषित कर सकते हैं जो माप की विभिन्न इकाइयों का प्रतिनिधित्व करते हैं, और सामान्य प्रकार के तर्क और बाधाओं के रूप में उपयोग किए जा सकते हैं; ITemperatureConverter दो प्रकार के लिए जेनेरिक हो सकता है, जिनमें से दोनों तापमान स्केल कक्षाओं के लिए बाध्य हैं, और सेल्सियस फारेनहाइट कनवर्टर कार्यान्वयन जेनेरिक इंटरफेस को सेल्सियस डिग्री और फारेनहाइट डेग्री दोनों को तापमान स्केल से व्युत्पन्न कर देगा। इससे आपको माप की इकाइयों को एक रूपांतरण की बाधाओं के रूप में प्रकट करने की अनुमति मिलती है, बदले में माप की इकाइयों के प्रकार के बीच रूपांतरण की अनुमति मिलती है (कुछ सामग्रियों की कुछ इकाइयों को रूपांतरण ज्ञात होते हैं; 1 ब्रिटिश शाही पिंट का वजन 1.25 पाउंड होता है)।

ये सभी डिज़ाइन निर्णय हैं जो इस डिज़ाइन में एक प्रकार के परिवर्तन को सरल बना देंगे, लेकिन कुछ लागत पर (या तो कुछ और करने के लिए कठिन या एल्गोरिदम प्रदर्शन कम करना)। यह क्या, आप के लिए वास्तव में "आसान" है समग्र आवेदन के संदर्भ और कोडिंग पर्यावरण, जिसमें आप काम में फैसला करने के लिए आप पर निर्भर है

संपादित करें:। उपयोग आप अपने संपादन से चाहते हैं,, तापमान के लिए बहुत आसान है । हालांकि, यदि आप एक सामान्य यूनिट कनवर्टर चाहते हैं जो किसी भी यूनिटफॉर्मर के साथ काम कर सके, तो अब आप एंट्स को माप की अपनी इकाइयों का प्रतिनिधित्व नहीं करना चाहते हैं, क्योंकि एम्स के पास कस्टम विरासत पदानुक्रम नहीं हो सकता है (वे सीधे System.Enum से प्राप्त होते हैं)।

आप निर्दिष्ट कर सकते हैं कि डिफ़ॉल्ट कन्स्ट्रक्टर किसी भी एनम को स्वीकार कर सकता है, लेकिन फिर आपको यह सुनिश्चित करना होगा कि एनम एक प्रकार की इकाई है जो माप की इकाई है, अन्यथा आप डायलॉग रेसल्ट वैल्यू में पास हो सकते हैं और कनवर्टर फ्रीक रनटाइम पर बाहर।

इसके बजाय, यदि आप एक यूनिट कनवर्टर चाहते हैं जो माप के अन्य इकाइयों के लिए किसी भी यूनिटऑफमेजर को लैम्बडास में परिवर्तित कर सकता है, तो मैं माप की इकाइयों को "मार्कर क्लास" के रूप में निर्दिष्ट करता हूं; छोटे स्टेटलेस "टोकन" है कि केवल में अर्थ है कि वे अपने स्वयं के प्रकार के होते हैं और उनके माता पिता से निकाले जाते हैं:

//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); } 
} 

आप त्रुटि-जांच में डाल सकते हैं (जाँच करें कि इनपुट यूनिट एक रूपांतरण निर्दिष्ट है, जाँच एक है कि रूपांतरण जोड़ा जा रहा है एक यूओएम के लिए मूल प्रकार के रूप में एक ही माता पिता के साथ, आदि) जैसा कि आप फिट देखते हैं। आप यूनिट कनवर्टर को तापमान कनवर्टर बनाने के लिए भी प्राप्त कर सकते हैं, जिससे आप स्थिर, संकलन-समय प्रकार के चेक जोड़ सकते हैं और रन-टाइम चेक से बचने की अनुमति दे सकते हैं जो यूनिट कनवर्टर का उपयोग करना होगा।

+0

+1 महान प्रतिक्रिया के लिए और आपकी टिप्पणियां वास्तव में उन बिंदुओं के साथ मदद करती हैं जिन्हें मैंने – Pricey

0

आम तौर पर मैं डैनी tuppeny की पोस्ट पर टिप्पणी के रूप में जोड़ना चाहते थे लेकिन वह ऐसा लगता है कि मैं इसे टिप्पणी के रूप में जोड़ने में सक्षम नहीं हूं।

मैंने @Danny Tuppeny से थोड़ा सा समाधान सुधार लिया। मैं दो रूपांतरण कारकों के साथ प्रत्येक रूपांतरण जोड़ना नहीं चाहता था, क्योंकि केवल एक आवश्यक होना चाहिए। इसके अलावा प्रकार Func का पैरामीटर आवश्यक नहीं लगता है, यह केवल उपयोगकर्ता के लिए यह अधिक जटिल बनाता है।

तो होगा मेरे कॉल लगता है:

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); 
    } 
} 

मैं भी इकाइयों के लिए के बीच रूपांतरण कारक पाने के लिए एक विधि गयी। यह संशोधित UnitConverter वर्ग है:

/// <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

एक एक भौतिक इकाइयों सामान्य प्रकार परिभाषित कर सकते ऐसा है कि, अगर एक प्रत्येक इकाई के लिए एक प्रकार है जो new लागू करता है और उस इकाई और एक "आधार इकाई के बीच एक अनुवाद विधि भी शामिल है "उस प्रकार का, कोई अलग-अलग इकाइयों में व्यक्त किए गए मानों पर अंकगणित कर सकता है और उन्हें टाइप सिस्टम का उपयोग करके आवश्यक रूप से परिवर्तित कर सकता है, जैसे AreaUnit<LengthUnit.Inches> प्रकार का चर केवल स्क्वायर इंच में आयामों को स्वीकार करेगा, लेकिन अगर कोई myAreaInSquareInches= AreaUnit<LengthUnit.Inches>.Product(someLengthInCentimeters, someLengthInFathoms); कहता है गुणा करने से पहले स्वचालित रूप से उन अन्य इकाइयों का अनुवाद करेगा। विधि-कॉल सिंटैक्स का उपयोग करते समय यह वास्तव में बहुत अच्छी तरह से काम कर सकता है क्योंकि Product<T1,T2>(T1 p1, T2 p2) विधि जैसी विधियां जेनेरिक प्रकार पैरामीटर को उनके ऑपरेंड स्वीकार कर सकती हैं। दुर्भाग्यवश, ऑपरेटर जेनेरिक बनाने का कोई तरीका नहीं है, न ही AreaUnit<T> where T:LengthUnitDescriptor जैसे किसी अन्य प्रकार के मनमाने ढंग से जेनेरिक प्रकार AreaUnit<U> से रूपांतरण के माध्यम को परिभाषित करने के लिए कोई तरीका नहीं है। एक AreaUnit<T> रूपांतरणों को परिभाषित कर सकता है उदा। AreaUnit<Angstrom>, लेकिन कंपाइलर को यह नहीं बताया जा सकता कि कोड को AreaUnit<Centimeters> and wants दिया गया है AreaUnit` इंच को एन्स्ट्रोम्स में परिवर्तित कर सकता है और फिर सेंटीमीटर तक।

1

आप Units.NET पर एक नज़र डाल सकते हैं। यह GitHub और NuGet पर है। यह सबसे आम इकाइयों और रूपांतरण प्रदान करता है, स्थिर टाइपिंग और इकाइयों की गणना और संक्षेप में पार्सिंग/प्रिंटिंग दोनों का समर्थन करता है। हालांकि यह अभिव्यक्तियों का विश्लेषण नहीं करता है, और आप इकाइयों के मौजूदा वर्गों का विस्तार नहीं कर सकते हैं, लेकिन आप इसे नई तीसरी पार्टी इकाइयों के साथ बढ़ा सकते हैं।

उदाहरण रूपांतरण:

Length meter = Length.FromMeters(1); 
double cm = meter.Centimeters; // 100 
double feet = meter.Feet; // 3.28084 
संबंधित मुद्दे

 संबंधित मुद्दे