Преобразовать класс со строковым свойством в другой класс с типизированным значением свойства - PullRequest
0 голосов
/ 15 апреля 2019

Имеются следующие классы:

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 является примером типизированного параметра, который может существовать, где значением является Enum из ArmingStatuses. Другие типы, которые могут существовать: DateTimes, int32, double и т. Д.

Я уже выполнил преобразование из типизированного значения в строку, но сейчас я пытаюсь, как правильно выполнить преобразование из строкового в типизированное значение.

Пробовал разные подходы:

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

Вопросы:

  • Все Ифс дают мне предупреждение, гласящее: «Данное выражение никогда не бывает предоставленным».
  • Невозможно сделать актерский состав (TValue). Он говорит, что не может конвертировать int в TValue. Единственное решение заключается в создании ценности с помощью отражения?

1 Ответ

0 голосов
/ 16 апреля 2019

Вот моя попытка сделать эту работу - я не уверен, что она нарушает некоторые детали, которые вы не объясняли (или объясняли).Поскольку параметры 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
}
...