在通用参数上编写具有两种可能的约束集的通用方法

时间:2018-12-23 11:30:04

标签: c# generics generic-programming

我正在寻求编写一个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

但是这仍然很丑陋,因为我不得不再次记住我正在阅读的内容,而不必记住函数签名,而必须记住将值类型装箱。

理想的解决方案是当我可以向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 :(得分:2)

如果您需要一个用于处理引用类型的方法和一个用于处理值类型的方法,那是拥有两个方法的完全正确的理由。

从代码的角度来看可能会有所帮助,该代码将调用此类中的方法。从他们的角度来看,如果他们可以只调用一个方法而与类型无关,而不必为值类型调用一个方法而为值类型调用另一个方法,则它们是否受益?可能不是。

发生的事情(而且我已经做过很多次了)是因为我们陷入与某种实际软件无关的原因,我们想要某个类的外观或行为试图写。以我的经验,在尝试编写泛型类时,这种情况经常发生。在我们使用的类型无关紧要的情况下(例如,如果对于一个int列表使用一个类,对于一个double列表使用另一个类等),当我们看到不必要的代码重复时,泛型类将为我们提供帮助

然后,当我们开始实际使用我们创建的类时,我们可能会发现我们的需求与我们所想的不完全相同,并且我们花费大量时间来完善该泛型类也浪费了时间。

如果我们正在使用的类型确实需要完全不同的代码,那么将多个不相关的类型强制处理为单个通用方法将使您的代码更加复杂。 (每当我们被迫使用dynamic时,就表明某些事情可能变得过于复杂,这是一个好兆头。)

我的建议只是编写所需的代码,而不用担心是否需要调用其他方法。看看是否确实造成了问题。可能不会。在问题出现之前,请不要尝试解决问题。