关于父子:在C#4.0中,有没有办法让一个类的私有成员只能用于特定的其他类?

In C# 4.0, is there any way to make an otherwise private member of one class available only to a specific other class?

我们正在创建一个对象层次结构,其中每个项都有一个其他项的集合,并且每个项也有一个指向其父项的Parent属性。相当标准的东西。我们还有一个从Collection继承的ItemsCollection类,它本身具有指向集合所属项目的Owner属性。再说一遍,那里没有什么有趣的。

当一个项添加到ItemsCollection类中时,我们希望它自动设置该项的父级(使用集合的Owner属性),当该项被删除时,我们希望清除父级。

事情是这样的。我们只希望Parentsetter对ItemsCollection可用,而不需要其他任何设置。这样,我们不仅可以知道一个项目的父级是谁,而且还可以通过检查Parent中的现有值,或让某人随意将其更改为其他值,确保一个项目不会添加到多个集合中。

我们知道如何做到这一点的两种方法是:

  • 将setter标记为private,然后将集合定义包含在项本身的范围内。专业:全面保护。反对:带有嵌套类的丑陋代码。

  • 在只有ItemsCollection知道的项目上使用私有ISetParent接口。专业:代码更清晰,易于理解。con:从技术上讲,任何知道该接口的人都可以调用Item,并访问setter。

  • 从技术上讲,通过思考,任何人都可以得到任何东西,但是…试图找到最好的方法。

    现在我知道C++中有一个特性叫做EDCOX1(12),或者让你指定一个类中的另一个私有成员作为另一个类,这将是一个完美的场景,但是我不知道C中有任何这样的事情。

    在伪代码中(例如,为了简洁起见,所有的属性更改通知都被删除了,我只是在这里键入,而不是从代码中复制),我们有这个……

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    public class Item
    {
        public string Name{ get; set; }
        public Item Parent{ get; private set; }
        public ItemsCollection ChildItems;

        public Item()
        {
            this.ChildItems = new ItemsCollection (this);
        }
    }

    public class ItemsCollection : ObservableCollection<Item>
    {
        public ItemsCollection(Item owner)
        {
            this.Owner = owner;
        }  

        public Item Owner{ get; private set; }

        private CheckParent(Item item)
        {
            if(item.Parent != null) throw new Exception("Item already belongs to another ItemsCollection");
            item.Parent = this.Owner; // <-- This is where we need to access the private Parent setter
        }

        protected override void InsertItem(int index, Item item)
        {
            CheckParent(item);
            base.InsertItem(index, item);
        }

        protected override void RemoveItem(int index)
        {
            this[index].Parent = null;
            base.RemoveItem(index);
        }

        protected override void SetItem(int index, Item item)
        {
            var existingItem = this[index];

            if(item == existingItem) return;

            CheckParent(item);
            existingItem.Parent = null;

            base.SetItem(index, item);
        }

        protected override void ClearItems()
        {
            foreach(var item in this) item.Parent = null; <-- ...as is this
            base.ClearItems();
        }

    }

    还有其他类似的方法吗?


    我每天都要解决你的问题,但我不是像你那样做的。

    后退一步。你想解决的根本问题是什么?一致性。您试图确保"x是y的子级"关系,"y是x的父级"关系始终是一致的。这是一个明智的目标。

    假设每个项都直接知道其子项和父项,因为子集合和父引用存储在本地字段中。这在逻辑上要求当项x成为项y的子项时,必须同时更改x.父项和y.子项。这就出现了你遇到的问题:谁来"负责"确保这两个变更的一致性?如何确保只有"负责"代码才能改变父字段?

    狡猾的

    假设我们否认你的假设。不必每个项目都知道它的子项和父项。

    技术第1章:

    例如,你可以说有一个特殊的项目叫做"宇宙",它是除自身之外的所有项目的祖先。"宇宙"可能是一个存储在一个众所周知的位置的单子。当您请求一个项目作为它的父项时,实现可以找到宇宙,然后搜索宇宙中的每个子代以查找该项目,并在您前进时跟踪路径。当你找到物品时,很好,你完成了。你回头看一步"路径",你就得到了父母。更好的是,如果需要,您可以提供整个父链;毕竟,您只是计算了它。

    技术第2章:

    如果宇宙是大的,而且要花一段时间才能找到每一个东西,那可能会很昂贵。另一种解决方案是让Universe包含一个将项目映射到其父级的哈希表,以及另一个将项目映射到其子级列表的哈希表。当您将子项x添加到父项y时,"添加"方法实际上调用了Universe并说"嘿,项x现在由y作为父项",Universe负责更新哈希表。项目不包含任何它们自己的"连通性"信息;这是宇宙强制执行的责任。

    另一方面,宇宙有可能包含循环;你可以告诉宇宙x是y的亲子,y是x的亲子。如果你想避免这个问题,那么你必须写一个循环检测器。

    技术第3章:

    你可以说有两棵树,"真正的"树和"正面的"树。真正的树是不变的和持久的。在真正的树中,每个项目都知道它的子项,但不知道它的父项。一旦构建了不变的实树,就可以创建一个外观节点,作为实树根的代理。当您请求该节点作为其子节点时,它会将一个新的外观节点包装在每个子节点上,并将外观节点的父属性设置为查询其子节点的节点。

    现在可以将外观树视为父级树,但父级关系仅在遍历树时计算。

    当您想要编辑树时,您会生成一个新的实树,尽可能多地使用旧的实树。然后创建一个新的门面根。

    这种方法的缺点是,只有在每次编辑之后,通常从上到下遍历树时,它才有效。

    我们在C和VB编译器中使用后一种方法,因为这正是我们所处的情况:当我们在代码编辑后重建分析树时,我们可以从前面的文本中重新使用现有的不可变分析树。我们总是从上到下遍历树,并且只想在必要时计算父引用。


    这里有一种方法可以模拟c中的friend

    标记您的属性internal,然后使用此属性将它们公开给friend程序集:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [assembly: InternalsVisibleTo("Friend1, PublicKey=002400000480000094" +
                                 "0000000602000000240000525341310004000" +
                                 "001000100bf8c25fcd44838d87e245ab35bf7" +
                                 "3ba2615707feea295709559b3de903fb95a93" +
                                 "3d2729967c3184a97d7b84c7547cd87e435b5" +
                                 "6bdf8621bcb62b59c00c88bd83aa62c4fcdd4" +
                                 "712da72eec2533dc00f8529c3a0bbb4103282" +
                                 "f0d894d5f34e9f0103c473dce9f4b457a5dee" +
                                 "fd8f920d8681ed6dfcb0a81e96bd9b176525a" +
                                 "26e0b3")]

    public class MyClass {
       // code...
    }


    C没有friend关键字,但它有一个非常接近的名称internal。可以将要以有限的方式公开的方法标记为内部方法,然后只有该程序集中的其他类型才能看到它们。如果您的所有代码都在一个程序集中,这对您没有太大帮助,但是如果这个类打包在一个单独的程序集中,它将工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Item
    {
        public string Name{ get; set; }
        public Item Parent{ get; internal set; } // changed to internal...
        public ItemsCollection ChildItems;

        public Item()
        {
            this.ChildItems = new ItemsCollection (this);
        }
    }


    我能想到的只有两件事:

    一:

    使用上面提到的第2种选项(我自己经常这样做),但是要使接口(item)的实现成为ItemsCollection中嵌套的私有类…这样,只有ItemsCollection知道setter。接口IItem只声明父级的getter…没有人可以将其强制转换为项,因为项对ItemsCollection是私有的。所以,有点像:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class ItemsCollection : ObservableCollection<IItem>
    {
        private class Item : IItem
        {
            public object Parent { get; set; }
        }

        private CheckParent(IItem item)
        {
            if(item.Parent != null) throw new Exception("Item already belongs to another ItemsCollection");
            ((Item)item).Parent = this.Owner; // <-- This is where we need to access the private Parent setter
        }

        public static IItem CreateItem() { return new Item(); }
    }

    public interface IItem
    {
        object Parent {get; }
    }

    当您希望ItemsCollection设置项父级时,将IItem实例设置为项(它确实公开了setter)。只有ItemsCollection可以进行这种转换,因为项目实现是ItemsCollection私有的…所以我认为这可以实现您想要的。

    二:

    把它设为内部而不是私有的…你不能得到你想要的,但是你可以使用InternalsVisibleToAttribute来表示内部成员对另一个程序集是可见的。


    我用来控制类成员可见性的一个解决方案是将类定义为分部,然后在另一个命名空间中将类声明为分部,并定义所需的特殊可见性成员。

    这将根据所选命名空间控制成员可见性。

    你唯一需要做的就是参考。它可能会变得复杂,但一旦你弄明白了,它就会起作用。


    我的答案是由两部分组成的

  • 为什么界面不安全?
  • 私有/内部访问修饰符用于防止错误,不是真正的"安全代码"。

    记得。。。您可以使用一些反射/调用等调用私有方法…

    .

    2。我通常把一切公开确保双方都知道如何处理彼此,

    当然,有一点乒乓球,但它只需要几个周期(在本例中,我有一个名称集)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
        private IPhysicalObject partOf;
        public IPhysicalObject PartOf
        {
            get { return partOf; }
            set
            {
                if (partOf != value)
                {
                    if (partOf != null)
                        partOf.Children.Remove(this.Designation);

                    partOf = value;

                    if (partOf != null)
                        partOf.Children.Add(this.Designation);
                }
            }
        }

        public virtual void Add(String key, IPhysicalObject value)
        {
            IPhysicalObject o;
            if (!TryGetValue(key, out o))
            {
                innerDictionary.Add(key, value);
                value.PartOf = Parent;
            }
        }

        public virtual bool Remove(String key)
        {
            IPhysicalObject o;
            if(TryGetValue(key, out o))
            {
                innerDictionary.Remove(key);
                o.PartOf = null;
            }
        }

    希望这有帮助…很好的一天,汤姆W

    另外,我永远也不知道这个编辑是怎么工作的…我应该用HTML还是不应该?


    您可以使用委托执行以下操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    public delegate void ItemParentChangerDelegate(Item item, Item newParent);

    public class Item
    {
        public string Name{ get; set; }
        public Item Parent{ get; private set; }
        public ItemsCollection ChildItems;

        static Item()
        {
            // I hereby empower ItemsCollection to be able to set the Parent property:
            ItemsCollection.ItemParentChanger = (item, parent) => { item.Parent = parent };
            // Now I just have to trust the ItemsCollection not to do evil things with it, such as passing it to someone else...
        }
        public static void Dummy() { }

        public Item()
        {
            this.ChildItems = new ItemsCollection (this);
        }
    }

    public class ItemsCollection : ObservableCollection<Item>
    {
        static ItemsCollection()
        {
            /* Forces the static constructor of Item to run, so if anyone tries to set ItemParentChanger,
            it runs this static constructor, which in turn runs the static constructor of Item,
            which sets ItemParentChanger before the initial call can complete.*/

            Item.Dummy();
        }
        private static object itemParentChangerLock = new object();
        private static ItemParentChangerDelegate itemParentChanger;
        public static ItemParentChangerDelegate ItemParentChanger
        {
            private get
            {
                return itemParentChanger;
            }
            set
            {
                lock (itemParentChangerLock)
                {
                    if (itemParentChanger != null)
                    {
                        throw new InvalidStateException("ItemParentChanger has already been initialised!");
                    }
                    itemParentChanger = value;
                }
            }
        }

        public ItemsCollection(Item owner)
        {
            this.Owner = owner;
        }  

        public Item Owner{ get; private set; }

        private CheckParent(Item item)
        {
            if(item.Parent != null) throw new Exception("Item already belongs to another ItemsCollection");
            //item.Parent = this.Owner;
            ItemParentChanger(item, this.Owner); // Perfectly legal! :)
        }

        protected override void InsertItem(int index, Item item)
        {
            CheckParent(item);
            base.InsertItem(index, item);
        }

        protected override void RemoveItem(int index)
        {
            ItemParentChanger(this[index], null);
            base.RemoveItem(index);
        }

        protected override void SetItem(int index, Item item)
        {
            var existingItem = this[index];

            if(item == existingItem) return;

            CheckParent(item);
            ItemParentChanger(existingItem, null);

            base.SetItem(index, item);
        }

        protected override void ClearItems()
        {
            foreach(var item in this) ItemParentChanger(item, null);
            base.ClearItems();
        }


    如何确保只有项的当前集合才能孤立该项?这样,当该项属于某个集合时,任何其他集合都无法设置该项的父级。您可以使用某种类型的唯一密钥,这样第三方就不会参与其中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    public sealed class ItemsCollection : ObservableCollection<Item>
    {
        private Dictionary<Item, Guid> guids = new Dictionary<Item, Guid>();

        public ItemsCollection(Item owner)
        {
            this.Owner = owner;
        }

        public Item Owner { get; private set; }

        private Guid CheckParent(Item item)
        {
            if (item.Parent != null)
                throw new Exception("Item already belongs to another ItemsCollection");
            //item.Parent = this.Owner; // <-- This is where we need to access the private Parent setter    
            return item.BecomeMemberOf(this);

        }

        protected override void InsertItem(int index, Item item)
        {
            Guid g = CheckParent(item);
            base.InsertItem(index, item);
            guids.Add(item, g);
        }

        protected override void RemoveItem(int index)
        {
            Item item = this[index];
            DisownItem(item);
            base.RemoveItem(index);
        }

        protected override void DisownItem(Item item)
        {
            item.BecomeOrphan(guids[item]);
            guids.Remove(item);            
        }

        protected override void SetItem(int index, Item item)
        {
            var existingItem = this[index];
            if (item == existingItem)
                return;
            Guid g = CheckParent(item);
            existingItem.BecomeOrphan(guids[existingItem]);
            base.SetItem(index, item);
            guids.Add(item, g);
        }

        protected override void ClearItems()
        {
            foreach (var item in this)
                DisownItem(item);
            base.ClearItems();
        }
    }




    public class Item
    {
        public string Name { get; set; }
        public Item Parent { get; private set; }

        public ItemsCollection ChildItems;

        public Item()
        {
            this.ChildItems = new ItemsCollection(this);
        }

        private Guid guid;

        public Guid BecomeMemberOf(ItemsCollection collection)
        {
            if (Parent != null)
                throw new Exception("Item already belongs to another ItemsCollection");
            Parent = collection.Owner;
            guid = new Guid();
            return guid; // collection stores this privately        
        }

        public void BecomeOrphan(Guid guid) // collection passes back stored guid        
        {
            if (guid != this.guid)
                throw new InvalidOperationException("Item can only be orphaned by its current collection");
            Parent = null;
        }
    }

    显然这里存在冗余;项集合正在存储第二个项集合(字典)。但是有很多方法可以克服我认为你能想到的困难。它就在这一点旁边。

    不过,我建议您考虑将子项管理的任务移到项类,并尽可能保持集合"哑"。

    编辑:针对您的疑问,如何防止和项目出现在两个ItemsCollection中:

    你问吉他的意义是什么。为什么不直接使用集合实例本身呢?

    如果将guid参数替换为集合引用,则可以向两个不同的集合添加一个项,如下所示:

    1
    2
    3
    4
    5
    6
    {
        collection1.InsertItem(item);  // item parent now == collection1
        collection2.InsertItem(item);  // fails, but I can get around it:
        item.BecomeOrphan(collection1); // item parent now == null
        collection2.InsertItem(item);  // collection2 hijacks item by changing its parent (and exists in both collections)
    }

    现在想象一下使用guid参数执行此操作:

    1
    2
    3
    4
    5
    {
        collection1.InsertItem(item);  // item parent now == collection1
        collection2.InsertItem(item);  // fails, so...
        item.BecomeOrphan(????); // can't do it because I don't know the guid, only collection1 knows it.
    }

    因此不能向多个itemsCollection添加项。并且itemscollection是密封的,因此不能对其进行子类化并重写其insert方法(即使这样做了,仍然无法更改项的父级)。


    这个场景让我印象深刻的第一件事是,itemcollection和item之间存在明显的特性嫉妒。我理解您希望将子项添加到集合并将父项设置为自主操作的愿望,但实际上,我认为维护该关系的责任在于项,而不是项集合。

    我建议将项目上的子项目公开为只读集合(可能有IEnumerable),并将AddChild(Item child)RemoveChild(Item child)ClearChildren()等方法放在项目上。这就使得维护父类的责任与您不担心泄漏到其他类中的项有关。