创建一个有效的数据类型,用于按初始和结果单位 C# 查找转换因子

Creating an efficient datatype for looking up conversion factors by initial and resulting units C#

我正在尝试创建一个 windows 表单应用程序,它将测量单位从一种转换为另一种。我的表单具有导航按钮,我认为这些按钮会使它对用户友好。

当用户 select 从主菜单选择一个类别时,它会填充测量组合框的类别,并将默认测量单位设置到其他两个组合框中。

这些组合框允许用户select他们想要的测量单位。当他们选择距离等类别时,它会填充初始和结果测量单位组合框。

因此我已经创建了一些包含每个测量单位的string[]

string[] distanceUnits = { "Inches", "Feet", "Centimeters", "Meters", "Kilometers", "Miles" };
string[] massUnits = { "Grams", "Kilograms", "Pounds", "Ounces" };

我还创建了几个不同的 decimal[] 数组,其中填充了转换因子,自从我将进行单元测试以来,我一直在查找和编辑这些转换因子以实现精确目的。我希望能够轻松地更改转换因子或将新的转换因子添加到数组中。

例如,

decimal[] massConversionFactors =
{
    .001m,               // [0] Grams to KiloGrams
    .00220462m,          // [1] Grams to Pounds
    .035274m,            // [2] Grams to Ounces
    1000,                // [3] KiloGrams to Grams
    2.20462m,            // [4] KiloGrams to Pounds
    35.274m,             // [5] KiloGrams to Ounces
    453.592m,            // [6] Pounds to Grams
    .453592m,            // [7] Pounds to Kilograms
    16                   // [8] Pounds to Ounces
};

decimal[] distanceConversionFactors =
{
    .083333m,             // [0] Inches to Feet
    2.54m,                // [1] Inches to Centimeters
    .0254m,               // [2] Inches to Meters
    .0000254m,            // [3] Inches to Kilometers
    .0000157828282828m,   // [4] Inches to Miles
    12,                   // [5] Feet to Inches
    30.48m,               // [6] Feet to Centimetes
    .3048m,               // [7] Feet to Meters
    .0003048m,            // [8] Feet to Kilometers
    .000189394m,          // [9] Feet to Miles
    .3937009133858m,      // [10] Centimeters to Inches
    .03280840944882m,     // [11] Centimeters to Feet
    .01m,                 // [12] Centimeters to Meters
    .00001m,              // [13] Centimeters to Kilometers
    .0000062137m,         // [14] Centimeters to Miles
    39.3701m,             // [15] Meters to Inches
    3.28084m,             // [16] Meters to Feet
    100,                  // [17] Meters to Centimeters
    .001m,                // [18] Meters to Kilometers
    .000621371m,          // [19] Meters to Miles
    39370.1m,             // [20] Kilometers to Inches
    3280.84m,             // [21] Kilometers to Feet
    100000,               // [22] Kilometers to Centimeters
    1000,                 // [23] Kilometers to Meters
    .621371m,             // [24] Kilometers to Miles
    63360,                // [25] Miles to Inches
    5280,                 // [26] Miles to Feet
    160934,               // [27] Miles to Centimeters
    1609.34m,             // [28] Miles to Meters
    1.60934m              // [29] Miles to Kilometers
};

这样,当按下提交按钮时,我可以进行如下计算:

private void ConvertSubmissionButton_Click(object sender, EventArgs e)
{
    //User's input value
    decimal input = decimal.Parse(UserInputTextBox.Text);
    string initialUnits = InitialUnitsComboBox.SelectedItem.ToString();
    string resultingUnits = ResultingUnitsComboBox.SelectedItem.ToString();

    //conversion of user input to result accepting initial and resulting
    decimal result =  input * ObjectWithTooManyMethods(initalUnits, resultingUnits);

    ResultingValueLabel.Text = (initialUnits);
    ResultingValueLabel.Show();
}

这目前工作正常,但随着我向对象添加更多方法,我想知道是否有一种方法可以合并已创建的数组。

我遇到的问题是我被迫对

中的每个转换排列进行硬编码
ObjectWithTooManyMethods(string, string);

我真正想做的是创建一个递归循环,它将遍历 distanceUnits[] massUnits[] 和其他度量单位类别数组,并根据类别的适当的 conversionFactor 数组。 这样我就可以添加测量单位及其换算系数,而无需在将来编写全新的方法。

这可能吗?

这个理想的循环将创建一个包含此信息的单一数据类型,并能够在用户单击提交按钮时检索初始单位和结果单位的转换因子的十进制值。

如:

DesiredDatatype<string,string,decimal> conversionFactors = new DesiredDatatype<string,string,decimal>()
{
    {initialUnits, resultingUnits, conversionFactor}
};

    private void ConvertSubmissionButton_Click(object sender, EventArgs e)
    {
        //User's input value
        decimal input = decimal.Parse(UserInputTextBox.Text);
        string initialUnits = InitialUnitsComboBox.SelectedItem.ToString();
        string resultingUnits = ResultingUnitsComboBox.SelectedItem.ToString();

        decimal result =  input * conversionFactors.get(initialUnits, resultingUnits);

        ResultingValueLabel.Text = (result);
        ResultingValueLabel.Show();
    }

也许我处理这个问题的方式不对,and/or 今天编码时间太长了。我查找了有关元组和字典的信息,它们似乎非常适合这种情况,但我有无法通过循环这些现有数组中的信息来创建工作数据类型。

非常感谢任何建议。非常感谢!

我认为为此在助手 class 中创建硬编码函数会更好。真的没有理由将所有这些常量放在一个动态数组中。这将是抽象的、令人困惑的并且难以跟踪。例如,您的批量转换 class 可能如下所示:

public static class MassConversion
{
    public static decimal GramsToKilograms(decimal grams) {
        return grams * .001;
    }
    public static decimal GramsToPounds(decimal grams) {
        return grams * .00220462;
    }
    public static decimal GramsToOunces(decimal grams) {
        return grams * .035274;
    }
    public static decimal KilogramsToGrams(decimal kilograms) {
        return kilograms* 1000;
    }
    //...
};

然后,使用它:

var grams = 4267;
var kilos = MassConversion.GramsToKilograms(grams);

这也有自我记录的好处。

在 C# 中工作时,我喜欢使用对象(在罗马时,对吗?)。

这是我 "quick and dirty" 解决对象问题的方法。正如@zzxyz 提到的,对于每个单位组,我们定义一个标准单位并计算与之相关的所有内容。

这种方法的巨大优势在于我们只定义了每个单元一次,而不是为所有可能的组合进行大量交叉引用 table。添加新单位很容易 - 只需定义它即可。

此外,无需为每次调用都硬编码一个函数...只需设置 Convert 方法调用的参数即可:

weight.Convert(400, "kg", "g");

就对象而言,我考虑过为 Units 提供名称和缩写属性,但为了速度起见,我只保留了名称。但是在填充它时我使用了缩写。

输出:

给运行它:

var app = new App_Conversion(); app.Run();

using System;
using System.Collections.Generic;
using System.Linq;

namespace Whosebug
{
    public class App_Conversion
    {
        public void Run()
        {
            var weight = getWeightUnitGroup();

            var w = weight.Convert("g", "kg");
            Console.WriteLine(w.ToString());

            var x = weight.Convert(400, "kg", "g");
            Console.WriteLine(x.ToString());

            var y = weight.Convert(12, "lb", "g");
            Console.WriteLine(y.ToString());

            var z = weight.Convert(7, "g", "lb");
            Console.WriteLine(z.ToString());
        }   

        private UnitGroup getWeightUnitGroup() 
        // we could also get the data from wherever and deserialize into these classes       
        {
            //base unit for the weight Unit Group
            var gram = new Unit("g", 1);

            var weight = new UnitGroup("Weight", UnitGroup.eType.Mass, gram);

            weight.AddUnit(new Unit("kg", 1000m));
            weight.AddUnit(new Unit("mg", 0.001m));
            weight.AddUnit(new Unit("oz", 28.35m));
            weight.AddUnit(new Unit("lb", 453.59m));

            return weight;
        }
    }

    public class UnitGroup
    {
        public enum eType
        {
            Mass = 1,
            Distance = 2
        }

        public eType Type { get; private set; }

        public string Name { get; private set; }

        public Unit BaseUnit { get; private set; }

        public List<Unit> Units { get; private set; } = new List<Unit>();

        public UnitGroup(string name, eType type, Unit baseUnit)
        {
            Name = name;
            Type = type;
            BaseUnit = baseUnit;
            AddUnit(baseUnit);
        }

        public void AddUnit(Unit unit)
        {
            Units.Add(unit);
            unit.AddToGroup(this);
        }

        public ConversionResult Convert(string fromName, string toName)
        {
            return Convert(1, fromName, toName);
        }

        public ConversionResult Convert(decimal qty, string fromName, string toName)
        {
            var from = Units.Where(u => u.Name.Equals(fromName)).First();

            var to = Units.Where(u => u.Name.Equals(toName)).First();

            var result = Decimal.Round(qty * (from.Quantity * to.ConvertToFactor),5);

            var formattedResult = $"{qty} {fromName} = {result} {toName}";

            return new ConversionResult(result, formattedResult);
        }
    }

    public class Unit
    {        
        public UnitGroup Group { get; private set; }

        public string Name { get; private set; }        

        public decimal Quantity { get; private set; }

        public decimal ConvertToFactor
        {
            get
            {
                return Group.BaseUnit.Quantity / Quantity;
            }
        }

        public Unit(string name, decimal qty)
        {
            Name = name;
            Quantity = qty;
        }

        public void AddToGroup(UnitGroup group)
        {
            Group = group;
        }
    }

    public class ConversionResult
    {
        private string formattedResult;

        public decimal Result { get; private set; }

        public ConversionResult(decimal result, string formattedResult)
        {
            Result = result;
            this.formattedResult = formattedResult;
        }

        public override string ToString()
        {
            return formattedResult;
        }
    }
}

既然不想用对象,那用枚举和字典呢?您可以像这样定义一个枚举...

public enum MassUnit
{
    Grams,
    Kilograms,
    Ounces,
    Pounds
}

要将枚举值与字符串相互转换,您可以...

var stringArrayOfEnumNames = Enum.GetNames(typeof(Mass));
var enumFromString = Enum.Parse(typeof(MassUnit), "Ounces");

接下来您可以使用字典来计算您的转换金额。无需转换的每个组合,只需转换为标准单位(克),然后再转换为目标单位。此字典存储值以将任何质量单位转换为克。

public static Dictionary<MassUnit, decimal> MassToGramsMap = new Dictionary<MassUnit, decimal>
{
    [MassUnit.Grams] = 1m,
    [MassUnit.Kilograms] = .001m,
    [MassUnit.Ounces] = .035274m,
    [MassUnit.Pounds] = .00220462m
};

然后转换看起来像...

var value = 100m;   
var from = MassUnit.Pounds;
var to = MassUnit.Kilograms;
var convertedValue = value / MassToGramsMap[from] * MassToGramsMap[to];

现在,如果您想添加一个新的质量单位,您可以将其添加到 MassUnit 枚举并将其转换添加到 MassToGramsMap 字典,这样就完成了。