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



具有以下类:

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是一种抽象,用于在参数值的C# 上使用类型化值(TValue),而不是使用我们从数据库中获取的字符串。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是可以存在的类型化参数的示例,其中的值是 ArmingStatuses 的枚举。可以存在的其他类型是DateTimes,int32,double等。

我已经完成了从类型化值到字符串的转换,但现在我正在努力如何正确地从字符串到类型化值进行转换。

尝试了不同的方法:

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

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

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

问题:

  • 所有的如果都给了我一个警告,说:"给定的表达永远不会是提供的"。
  • 无法制作演员表(电视)。它说不能将int转换为TValue。唯一的解决方案是通过反思创造价值?

这是我尝试完成这项工作 - 我不确定它是否违反了您没有解释(或确实解释)的一些细节。由于out参数不能使用多态性,因此我创建了一个接口来表示类型化参数基类中的通用函数。由于没有静态虚拟方法,我使用了对象方法并创建了一个结果对象,如果可以转换,则将使用该对象。

我认为转换方法没有理由有多个实例或需要一个接口,所以我将其创建为单个静态方法。我使用enum来捕获从传入类型可访问的参数所需的转换类型,并且必须通过object进行棘手的转换来处理对out参数值字段的赋值,因为 C# 没有赋值的类型切换功能。请注意,如果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;
}
}

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

var as_tv = new DeviceParameter() {
Key = "testkey",
DeviceId = new Guid(),
Value = "Armed"
};
var asp = new ArmingStatusParameter();
if (DeviceValueTypedParameterConverter.TryConvert<ArmingStatuses>(as_tv, asp)) {
// work with asp
}
var po_tv = new DeviceParameter() {
Key = "testkey2",
DeviceId = new Guid(),
Value = "4/15/2019 17:36"
};
var pop = new PoweredOnStatusParameter();
if (DeviceValueTypedParameterConverter.TryConvert<DateTime>(po_tv, pop)) {
// work with pop
}
var v_tv = new DeviceParameter() {
Key = "testkey3",
DeviceId = new Guid(),
Value = "17"
};
var vp = new VoltageStatusParameter();
if (DeviceValueTypedParameterConverter.TryConvert<int>(v_tv, vp)) {
// work with vp
}

最新更新