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

Mar*_*eIV 20 c# private friend parent-child

我们正在创建一个对象层次结构,其中每个项目都有一个其他项目的集合,每个项目也有一个Parent指向其父项目的属性.很标准的东西.我们还有一个ItemsCollection继承自的类,该类Collection<Item>本身具有Owner指向集合所属项的属性.再一次,没有什么有趣的.

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

这就是事情.我们只希望Parent设置器可用ItemsCollection,没有别的.这样,我们不仅可以知道项目的父项是谁,还可以通过检查现有值Parent,或者让某人随意将其更改为其他项目来确保项目不会添加到多个集合中.

我们知道如何做到的两种方式是:

  1. 将setter标记为private,然后将集合定义包含在项目本身的范围内.亲:全面保护.Con:嵌套类的丑陋代码.

  2. ISetParent在仅ItemsCollection知道的Item上使用私有接口.亲:更清晰的代码,易于遵循.Con:从技术上讲,任何了解界面的人都可以投射Item并获得制定者.

现在技术上通过反思任何人都可以得到任何东西,但仍然......试图找到最好的方法来做到这一点.

现在我知道有C++中的功能,称为Friend什么,让你在一个类中指定的,否则私有成员为可用于另一这将是很好的方案,但我不知道在C#中的任何这样的事情.

在伪代码中(例如,所有属性都更改了通知,为了简洁而删除了这些通知,我只是在这里输入,而不是从代码中复制),我们有...

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();
    }

}
Run Code Online (Sandbox Code Playgroud)

做其他类似的事情吗?

Eri*_*ert 15

我必须每天解决你的问题,但我没有像你想要的那样去做.

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

您的假设是每个项目都直接知道其子项及其父项,因为子项集合和父项引用存储在本地字段中.这在逻辑上要求当项目x成为项目y的子项时,您必须始终如一地更改x.Parent和y.Children.这就是你遇到的问题:谁能够"掌控"确保两个变化都是一致的?那么如何确保只有 "负责"代码才能改变父字段?

棘手.

假设我们否认了你的假设.不一定是每个项目都知道其子项及其父项.

技术#1:

例如,你可以说有一个叫做"宇宙"的特殊项目,它是除了它自己以外的每个项目的祖先."宇宙"可以是存储在众所周知的位置的单身人士.当您向项目询问其父项时,实现可以找到该Universe,然后搜索Universe的每个后代以查找该项目,并随时跟踪该路径.当你找到这个项目时,很棒,你已经完成了.你看起来又向后退了一条"路径",那里有你的父母.更好的是,如果你愿意,你可以提供整个父母 ; 毕竟,你只是计算它.

技术#2:

如果宇宙很大并且需要一段时间才能找到每个项目,这可能会很昂贵.另一种解决方案是让Universe包含将项目映射到其父项的哈希表,以及将项目映射到其子项列表的第二个哈希表.当你将child x添加到父y时,"add"方法实际上调用Universe并说"嘿,项目x现在是y的父级",并且Universe负责更新哈希表.项目不包含任何自己的"连通性"信息; 这是宇宙执行的责任.

另一方面,宇宙有可能包含循环; 你可以告诉宇宙x是y的父级,y是x的父级.如果你想避免这种情况,那么你必须编写一个循环检测器.

技术#3:

你可以说有两棵树; "真正的"树和"门面"树.真正的树是不可变的和持久的.在真正的树中,每个项目都知道它的子项而不是它的父项.一旦构建了不可变的真实树,就可以创建一个Facade节点,它是真实树的根的代理.当您向其子节点请求该节点时,它会围绕每个子节点创建一个新的Facade节点,并将facade节点的父属性设置为为其子节点查询的节点.

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

当你要编辑的树,就产生一个新的真正的树,再利用尽可能多的旧真正的树尽可能的.然后,您创建一个新的Facade根.

这种方法的缺点是,只有在每次编辑后通常从顶部向下遍历树时,它才有效.

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


Jam*_*are 5

C#没有friend关键字,但它有一些关闭的东西internal.您可以将要以有限方式公开的方法标记为内部,然后只有该组件中的其他类型才能看到它们.如果您的所有代码都在一个程序集中,这对您没有多大帮助,但是如果这个类打包在一个单独的程序集中,它将起作用.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)


Mrc*_*ief 5

这是一种可以friend在C#中模拟的方法:

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

[assembly: InternalsVisibleTo("Friend1, PublicKey=002400000480000094" + 
                              "0000000602000000240000525341310004000" +
                              "001000100bf8c25fcd44838d87e245ab35bf7" +
                              "3ba2615707feea295709559b3de903fb95a93" +
                              "3d2729967c3184a97d7b84c7547cd87e435b5" +
                              "6bdf8621bcb62b59c00c88bd83aa62c4fcdd4" +
                              "712da72eec2533dc00f8529c3a0bbb4103282" +
                              "f0d894d5f34e9f0103c473dce9f4b457a5dee" +
                              "fd8f920d8681ed6dfcb0a81e96bd9b176525a" +
                              "26e0b3")]

public class MyClass {
   // code...
}
Run Code Online (Sandbox Code Playgroud)


Jef*_*eff 4

我能想到的只有两件事:

一:

使用上面提到的选项 2(我自己经常这样做)...但是使接口(Item)的实现成为...内部的嵌套私有类ItemsCollection...这样只ItemsCollection知道设置器。该接口IItem只为 Parent 声明一个 getter...并且没有人可以将其强制转换为 Item,因为 Item 是私有的ItemsCollection。所以,像这样:

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; }
}
Run Code Online (Sandbox Code Playgroud)

当您想要ItemsCollection设置项目父级时,将IItem实例设置为项目(这确实公开了一个setter)。只能ItemsCollection执行此转换,因为 Item 实现是私有的ItemsCollection......所以我认为这可以完成你想要的。

二:

使其成为内部的而不是私有的...您无法完全得到您想要的东西,但您可以使用它InternalsVisibleToAttribute来表示内部成员对另一个程序集是可见的。