【问题标题】:Generics - Cleaner way to implement mutual compile-time type safety?泛型 - 实现相互编译时类型安全的更简洁方法?
【发布时间】:2017-03-02 20:45:09
【问题描述】:

假设您有一个由“父母”和“孩子”组成的数据结构,其中父母和孩子之间的引用是相互的:

借用a previous post,编写如下代码保证父母和孩子之间的相互引用(少用Reflection):

public static class Base
{
    private interface IParent
    {
        List<Child> Children { get; }
    }

    public class Parent : IParent
    {
        private readonly List<Child> _children = new List<Child>();

        public readonly ReadOnlyCollection<Child> Children = _children.AsReadOnly();

        List<Child> IParent.Children { get { return _children; } }
    }

    public class Child
    {
        private Parent _parent;

        public Parent Parent
        {
            get
            {
                return _parent;
            }
            set
            {
                if(value == _parent)
                    return;

                if(_parent != null)
                {
                    ((IParent)_parent).Children.Remove(this);
                    _parent = null;
                }

                if(value != null)
                {
                    ((IParent)value).Children.Add(this);
                    _parent = value;
                }
            }
        }
    }
}

现在假设您想要一个类似的结构,但您还想要类型安全。也就是说,TParent 的实例只能引用 TChild 的实例,而 TChild 的实例只能引用 TParent 的实例。

我想出了这个解决方案:

public static class Base<TParent, TChild>
    where TParent : Base<TParent, TChild>.Parent
    where TChild  : Base<TParent, TChild>.Child
{
    private interface IParent
    {
        List<TChild> Children { get; }
    }

    public class Parent : IParent
    {
        private readonly List<TChild> _children = new List<Child>();

        public readonly ReadOnlyCollection<TChild> Children = _children.AsReadOnly();

        List<TChild> IParent.Children { get { return _children; } }
    }

    public class Child
    {
        private TParent _parent;

        public TParent Parent
        {
            get
            {
                return _parent;
            }
            set
            {
                if(value == _parent)
                    return;

                if(_parent != null)
                {
                    // Oh no, casting!
                    ((IParent)_parent).Children.Remove((TChild)this);
                    _parent = null;
                }

                if(value != null)
                {
                    // Oh no, casting!
                    ((IParent)value).Children.Add((TChild)this);
                    _parent = value;
                }
            }
        }
    }
}

虽然这有效,但 ChildChild.Parent.set 内被转换为 TChild 的点让我有点担心。虽然我不确定有没有办法使用这个抛出 InvalidCastException 的类,但它可能仍然无法破解。

有没有更简洁的方法来实现这个效果?

【问题讨论】:

  • 天哪,请不要这样做。请参阅 blogs.msdn.microsoft.com/ericlippert/2011/02/03/… 了解为什么这是 C# 中的错误模式。
  • @EricLippert - 你确实说过,“在实践中,有时使用这种模式确实可以以其他方式在 C# 中难以建模的方式切实解决问题”。当我指定对象模型而其他人只是使用它时,我发现这种方法很有用。这是否减轻了您看到的问题?

标签: c# generics type-safety


【解决方案1】:

首先,问题Generics and Parent/Child architecture 的答案对类似于您的用于父子关系的通用建模的架构有一些想法。

其次,我强烈建议不要使用这种模式。这令人困惑,可以说是对泛型的滥用,而且它实际上不是类型安全的。见https://blogs.msdn.microsoft.com/ericlippert/2011/02/03/curiouser-and-curiouser/

第三,您认为编译器可能错误地认为需要进行类型转换,并且可能不可能导致它失败,这种态度可能会导致您在未来陷入困境。我建议相反,当它说你的程序不是类型安全的时,默认相信编译器是正确的,并且如果你认为编译器是错误的,那么相信你可能没有从头到尾考虑过:

class Car : Base<Car, Wheel>.Parent { ... }
class Wheel : Base<Car, Wheel>.Child { ... }
class AnotherWheel : Base<Car, Wheel>.Child { ... }
...
new Car() blah blah blah
new Wheel() blah blah blah
the wheel is the child of the car, blah blah blah
and what happens when you make a Car the parent of an AnotherWheel?

编译器说强制转换是完全正确的;我们不能断定转换是有效的,因为它不是。

第四,如何在不滥用实际上不是类型安全的泛型的情况下解决这个问题?

有很多方法可以做到,但我认为你不会喜欢它们。例如:

  • 制作底层不可变对象。孩子不认识父母;父母确实认识自己的孩子。
  • 在底层不可变对象之上构造一个外观对象;孩子的门面确实知道它的父母。
  • “编辑”立面时,会通过构建树的新脊来构建新的底层不可变树。

可以使用通用接口协变构造这样的系统,这样就不需要强制转换;这样做留作练习。

作为一个热身练习,考虑如何为通用不可变 IStack&lt;T&gt; 构造一个接口,这样当您将 Turtle 推到一堆 Tigers 上时,它就会变成一个不可变的 Animals 堆栈。

总的来说,我认为您最好不要尝试在 C# 类型系统中捕获这种限制。

【讨论】:

  • 你的例子听起来很像你在Red-Green Trees上的博客文章。
  • 你能澄清你的建议(在“第四”下)吗?也许使用代码示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多