Написание универсального метода с двумя возможными наборами ограничений на универсальный аргумент - PullRequest
0 голосов
/ 23 декабря 2018

Я нахожусь в поиске написания TypedBinaryReader, который мог бы читать любой тип, который обычно поддерживает BinaryReader, и тип, который реализует определенный интерфейс.Я подошел очень близко, но я еще не совсем там.

Для типов значений я сопоставил типы с функторами, которые вызывают соответствующие функции.

Для ссылочных типов, какПока они наследуют указанный мной интерфейс и могут быть сконструированы, функция ниже работает.

Однако я хочу создать универсальный универсальный вызов метода ReadUniversal<T>(), который будет работать как для типов значений, так и для указанных выше.ссылочные типы.

Это попытка номер один, она работает, но она не является достаточно общей, мне все еще приходится заниматься делами.

public class TypedBinaryReader : BinaryReader {

        private readonly Dictionary<Type, object> functorBindings;

        public TypedBinaryReader(Stream input) : this(input, Encoding.UTF8, false) { }

        public TypedBinaryReader(Stream input, Encoding encoding) : this(input, encoding, false) { }

        public TypedBinaryReader(Stream input, Encoding encoding, bool leaveOpen) : base(input, encoding, leaveOpen) {
            functorBindings = new Dictionary<Type, object>() {
                {typeof(byte), new Func<byte>(ReadByte)},
                {typeof(int), new Func<int>(ReadInt32)},
                {typeof(short), new Func<short>(ReadInt16)},
                {typeof(long), new Func<long>(ReadInt64)},
                {typeof(sbyte), new Func<sbyte>(ReadSByte)},
                {typeof(uint), new Func<uint>(ReadUInt32)},
                {typeof(ushort), new Func<ushort>(ReadUInt16)},
                {typeof(ulong), new Func<ulong>(ReadUInt64)},
                {typeof(bool), new Func<bool>(ReadBoolean)},
                {typeof(float), new Func<float>(ReadSingle)}
            };
        }


        public T ReadValueType<T>() {
            return ((Func<T>)functorBindings[typeof(T)])();
        }

        public T ReadReferenceType<T>() where T : MyReadableInterface, new() {
            T item = new T();
            item.Read(this);
            return item;
        }

        public List<T> ReadMultipleValuesList<T, R>() {
            dynamic size = ReadValueType<R>();
            List<T> list = new List<T>(size);
            for (dynamic i = 0; i < size; ++i) {
                list.Add(ReadValueType<T>());
            }

            return list;
        }

        public List<T> ReadMultipleObjecsList<T, R>() where T : MyReadableInterface {
            dynamic size = ReadValueType<R>();
            List<T> list = new List<T>(size);
            for (dynamic i = 0; i < size; ++i) {
                list.Add(ReadReferenceType<T>());
            }

            return list;
        }
}

Идея, которую я придумал, которую я не наденуна самом деле, это написать общий класс, который включает блоки в типах значений, как этот:

 public class Value<T> : MyReadableInterface {

        private T value;

        public Value(T value) {
            this.value = value;
        }

        internal Value(TypedBinaryReader reader) {
            Read(reader);
        }

        public T Get() {
            return value;
        }

        public void Set(T value) {
            if (!this.value.Equals(value)) {
                this.value = value;
            }
        }

        public override string ToString() {
            return value.ToString();
        }

        public void Read(TypedBinaryReader reader) {
            value = reader.ReadValueType<T>();
        }
    }

Таким образом, я могу использовать ReadReferencTypes<T>() даже для типов значений, пока я передаювведите параметр как Value<int> вместо просто int.

Но это по-прежнему ужасно, так как мне снова приходится вспоминать то, что я читаю, просто вместо того, чтобы запоминать сигнатуру функции, я должен помнитьфилиалx в типах значений.

Идеальным решением было бы добавление следующего метода к классу TypedBinaryReader:

public T ReadUniversal<T>() {
    if ((T).IsSubclassOf(typeof(MyReadableInterface)) {
        return ReadReferenceType<T>();
    } else if (functorBindings.ContainsKey(typeof(T)) {
        return ReadValueType<T>();
    } else {
        throw new SomeException();
    }
}

Однако из-за различных ограничений универсального аргумента T,это не сработает.Любые идеи о том, как заставить это работать?

Конечная цель состоит в том, чтобы прочитать любой тип, который обычно BinaryReader может или любой тип, который реализует интерфейс, используя только один метод.

1 Ответ

0 голосов
/ 23 декабря 2018

Если вам нужен метод для обработки ссылочных типов и метод для обработки типов значений, это совершенно веская причина иметь два метода.

Что может помочь, это посмотреть на это с точки зрения кода, который будет вызывать методы этого класса.С их точки зрения, выиграют ли они, если смогут вызывать только один метод независимо от типа вместо того, чтобы вызывать один метод для типов значений, а другой для типов значений?Вероятно, нет.

Что происходит (и я делал это много и много раз), так это то, что мы понимаем, как определенный класс должен выглядеть или вести себя по причинам, не связанным сфактическое программное обеспечение, которое мы пытаемся написать.По моему опыту, это часто случается, когда мы пытаемся написать общие классы.Общие классы помогают нам, когда мы видим ненужное дублирование кода в тех случаях, когда типы, с которыми мы работаем, не имеют значения (например, если у нас был один класс для списка целых чисел, другой для списка двойников и т. Д.)

Затем, когда мы действительно дойдем до с использованием созданных нами классов, мы можем обнаружить, что наши потребности не совсем такие, как мы думали, и время, которое мы потратили на полировку этого универсального класса, теряется.

Если типы, с которыми мы работаем, требуют совершенно другого кода, то принудительная обработка нескольких несвязанных типов в одном универсальном методе сделает ваш код более сложным.(Всякий раз, когда мы чувствуем себя вынужденными использовать dynamic, это хороший признак того, что что-то могло стать слишком сложным.)

Я предлагаю просто написать код, который вам нужен, и не беспокоиться, если вам нужно вызывать разные методы.Посмотрите, действительно ли это создает проблему.Это вероятно не будет.Не пытайтесь решить проблему, пока она не появится.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...