编写一个泛型方法,对泛型参数设置两组可能的约束

Writing a generic method with two possible sets of constraints on the generic argument

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

我提出的一个我不太喜欢的想法是编写泛型 class 在值类型中装箱,如下所示:

 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 class:

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 通常可以读取的任何类型或实现该接口的任何类型。

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

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

发生的事情(我已经做了很多很多次)是我们陷入了我们希望某个 class 看起来或行为的方式,原因与我们尝试编写的实际软件。根据我的经验,当我们尝试编写通用 classes 时,这种情况经常发生。当我们在使用的类型无关紧要的情况下看到不必要的代码重复时,泛型 classes 会帮助我们(比如如果我们有一个 class 用于 int 列表,另一个用于列表双打等)

然后,当我们开始实际 使用 我们创建的 class 时,我们可能会发现我们的需求与我们想象的不完全一样,而且时间我们花了很多时间来打磨通用 class 被浪费了。

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

我的建议是只写你需要的代码,如果你需要调用不同的方法,不要担心。看看它是否真的会造成问题。它可能不会。不要在问题出现之前尝试解决它。​​