将具有字符串属性的类转换为具有类型化属性值的另一类

时间:2019-04-15 16:05:41

标签: c# generics type-conversion

具有以下课程:

public class DeviceParameter
{
    public string Key { get; set; }

    public Guid DeviceId { get; set; }

    public string Value { get; set; }
}

设备可以具有很多不同类型的参数,但它们都以字符串形式存储在数据库中。

public abstract class DeviceValueTypedParameter<TValue>
{
    public string CodeName { get; }

    public TValue Value { get; set; }

    public Guid DeviceId { get; set; }

    public DeviceValueTypedParameter(string codeName)
    {
        this.CodeName = codeName;
    }
}

DeviceValueTypedParameter 是一个抽象,具有类型化的值 TValue 在参数的C#上使用值,而不是使用我们从数据库中获得的字符串。 DeviceValueTypedDeviceParameter和DeviceParameter之间没有继承关系,因为我想按组成将TValue转换为字符串。

public class ArmingStatusParameter : DeviceValueTypedParameter<ArmingStatuses>
{
    public const string CODE_NAME = "ArmingStatus";

    public ArmingStatusParameter() : base(CODE_NAME)
    {
    }
}

public enum ArmingStatuses
{
    Unknown,
    Armed,
    Disarmed,
}

ArmingStatusParameter 是可以存在的类型化Parameter的示例,其中值是ArmingStatuses的Enum。可以存在的其他类型为DateTimes,int32,double等。

我已经完成了从Typed值到String的转换,但是现在我在努力如何正确地进行从string到Typed值的转换。

尝试了不同的方法:

  1. 隐式或显式转换
  2. 扩展方法
  3. 每种类型的转换器类
  4. 基于TValue类型的通用转换器类

选项1:易于实现,但违反了
的POCO ArmingStatusParameter。人们可能会忘记实施隐式/显式运算符,并且错误只会在编译时发生。

选项2:违反了接口隔离原则(ISP),因为直接访问转换是必需的。

选项3:它可以工作,但是人们将不得不创建很多类,并且代码太冗长。对于每个不同的参数,都需要实例化一个新的{X} TypedParameterConverter。

选项4:似乎是最好的选择,但是我在“使其工作”方面遇到麻烦

我在想这样的事情:

public interface IDeviceValueTypedParameterConverter
{
    bool TryConvert<T, TValue>(DeviceParameter deviceParameter, 
        DeviceValueTypedParameter<TValue> deviceValueTypedParameter)
        where T : DeviceValueTypedParameter<TValue>;
}

public class DeviceValueTypedParameterConverter : IDeviceValueTypedParameterConverter
{
    public bool TryConvert<T, TValue>(DeviceParameter inputParameter, 
            DeviceValueTypedParameter<TValue> outputParameter)
            where T : DeviceValueTypedParameter<TValue>
    {
        bool result = true;
        if (inputParameter == null)
        {
            throw new NullReferenceException($"DeviceValueTypedParameter:'{typeof(T)}' must be initialized first");
        }

        if (inputParameter.Value is int)
        {
            result = int.TryParse(inputParameter.Value, out int temp);
            outputParameter.Value = (TValue)temp;
        }
        else if (inputParameter.Value is Enum)
        {
            // some other code to convert the Enum's
        }
        // more else ifs one for each type 
        // (...)
        else
        {
            result = false;
        }
        outputParameter.DeviceId = inputParameter.DeviceId;
        return result;
    }
}

问题:

  • 所有Ifs都给我警告:“给定的表达式绝不是所提供的”。
  • 无法进行强制转换(TValue)。它说不能将int转换为TValue。唯一的解决方案是通过反思创造价值?

1 个答案:

答案 0 :(得分:0)

这是我尝试进行的工作-我不确定它是否违反了您未解释(或确实解释)的某些细节。由于cast v参数不能使用多态性,因此我创建了一个接口来表示类型化参数基类中的常用功能。由于没有静态虚拟方法,因此我使用对象方法并创建了一个结果对象,该对象将在可能的转换时使用。

我没有看到转换方法具有多个实例或需要接口的理由,因此我将其创建为单个静态方法。我使用了out来捕获从传入类型访问的参数所需的转换类型,并且不得不通过enum进行棘手的转换以处理对object参数值的赋值字段,因为C#没有分配的类型切换功能。请注意,如果out方法不能正确过滤所有情况并且IsPossible失败,则可能会导致运行时错误。

ChangeType

现在您可以像这样使用它:

public enum ValueParseTypes {
    Enum,
    DateTime,
    Int
}

public interface IDeviceValueTypedDeviceParameter<TValue> {
    string CodeName { get; }
    TValue Value { get; set; }
    Guid DeviceId { get; set; }
    ValueParseTypes ParseType { get; set; }

    bool IsPossibleValue(DeviceParameter aValue);
}

public abstract class DeviceValueTypedDeviceParameter<TValue> : IDeviceValueTypedDeviceParameter<TValue> {
    public string CodeName { get; }
    public TValue Value { get; set; }
    public Guid DeviceId { get; set; }
    public ValueParseTypes ParseType { get; set; }

    public DeviceValueTypedDeviceParameter(string codeName, ValueParseTypes parseType) {
        this.CodeName = codeName;
        this.ParseType = parseType;
    }

    public virtual bool IsPossibleValue(DeviceParameter aValue) => false;
}

public class ArmingStatusParameter : DeviceValueTypedDeviceParameter<ArmingStatuses> {
    public const string CODE_NAME = "ArmingStatus";

    public ArmingStatusParameter() : base(CODE_NAME, ValueParseTypes.Enum) {
    }

    static HashSet<string> ArmingStatusesNames = Enum.GetNames(typeof(ArmingStatuses)).ToHashSet();
    public override bool IsPossibleValue(DeviceParameter aValue) => ArmingStatusesNames.Contains(aValue.Value);
}

public enum ArmingStatuses {
    Unknown,
    Armed,
    Disarmed,
}

public class PoweredOnStatusParameter : DeviceValueTypedDeviceParameter<DateTime> {
    public const string CODE_NAME = "PoweredOn";

    public PoweredOnStatusParameter() : base(CODE_NAME, ValueParseTypes.DateTime) {
    }

    public override bool IsPossibleValue(DeviceParameter aValue) => DateTime.TryParse(aValue.Value, out _);
}

public class VoltageStatusParameter : DeviceValueTypedDeviceParameter<int> {
    public const string CODE_NAME = "PoweredOn";

    public VoltageStatusParameter() : base(CODE_NAME, ValueParseTypes.Int) {
    }

    public override bool IsPossibleValue(DeviceParameter aValue) => Int32.TryParse(aValue.Value, out _);
}

public static class DeviceValueTypedParameterConverter {
    public static bool TryConvert<TValue>(DeviceParameter inputParameter, IDeviceValueTypedDeviceParameter<TValue> outputParameter)
            where TValue : struct {
        if (inputParameter == null)
            throw new ArgumentNullException(nameof(inputParameter));
        else if (outputParameter == null)
            throw new ArgumentNullException(nameof(outputParameter));

        bool result = false;
        if (outputParameter.IsPossibleValue(inputParameter)) {
            outputParameter.DeviceId = inputParameter.DeviceId;
            switch (outputParameter.ParseType) {
                case ValueParseTypes.Enum:
                    if (Enum.TryParse(inputParameter.Value, out TValue typedValue)) {
                        outputParameter.Value = typedValue;
                        result = true;
                    }
                    break;
                case ValueParseTypes.DateTime:
                    if (DateTime.TryParse(inputParameter.Value, out var dtValue)) {
                        outputParameter.Value = (TValue)Convert.ChangeType(dtValue, typeof(TValue));
                        result = true;
                    }
                    break;
                case ValueParseTypes.Int:
                    if (Int32.TryParse(inputParameter.Value, out var intValue)) {
                        outputParameter.Value = (TValue)Convert.ChangeType(intValue, typeof(TValue));
                        result = true;
                    }
                    break;
            }
        }

        return result;
    }
}