如何在C#中模仿“朋友”?

时间:2011-07-26 00:47:17

标签: c#

我需要在C#中实现一些C ++风格的“朋友”功能,并且正在寻找创意。

考虑两个密切相关的类:Node和NodePart。节点包含NodeParts,它通过公共AddPart()调用从其他系统添加,其中包含这些系统构建的部分。节点需要“进入”NodePart以执行一些非常具体的通知,NodePart将通过单独的虚拟保护方法将其分发到某些处理后的任何派生类。 (对于熟悉基于组件的游戏对象编程的人来说,这是同样的事情。)

我希望能够让NodePart为Node提供一种获取这些通知方法的方法,而不会让系统中的任何其他类型做到这一点。 Node无需访问任何其他NodePart内部,只需转发一些私人通知即可。

现在,把这些类放在一个程序集中并使用'internal'显然可以解决这个问题,但我正在寻找比这更好的模式。 (我真的不想为将来要做的每一组课程产生新的程序集。)

除了反射+调用,这是令人讨厌和脆弱的,你能想到什么其他模式来解决这个问题?我的狡猾的感觉告诉我接口是解决方案的一部分,但我想不出如何。

(注意:我通过默默无闻保证安全。这个系统不必100%防止滥用 - 足以阻止人们做他们不应该做的事。我们不建立除颤器此处。)

更新:以下许多答案需要多个程序集。正如我上面提到的,我真的很想避免这种情况。我不想为每个组件放置一个系统。我们有足够的东西,由于我们使用XAML,我无法沿着IL链接路线走下去。但无论如何,谢谢你的答案。 :)

更新2:我在Linqpad中搞砸了,根据下面的答案提出了几个选项。你喜欢哪个最差/最少?为什么?

选项1:已过时

#pragma warning disable 612 // doc this
public sealed class Node : NodePart.SystemAccess {
#pragma warning restore 612
    NodePart _part;

    public NodePart Part {
        get { return _part; }
        set { _part = value; NotifyAdded(_part); }
    }
}

public class NodePart {
    void NotifyAdded() { Console.WriteLine("Part added"); }

    [Obsolete] public class SystemAccess // doc this
    {
        protected void NotifyAdded(NodePart part) { part.NotifyAdded(); }
    }
}

不错。有点奇怪但奇怪的是非常狭窄。我倾向于这个,因为它比下一个选项更紧凑。

选项2:Access + Hack

public sealed class Node {
    static readonly NodePart.ISystemAccess _systemAccess;

    static Node() {
        _systemAccess = (NodePart.ISystemAccess)typeof(NodePart)
            .GetNestedTypes(BindingFlags.NonPublic)
            .Single(t => t.Name == "SystemAccess")
            .GetConstructor(Type.EmptyTypes)
            .Invoke(null);
    }

    NodePart _part;

    public NodePart Part {
        get { return _part; }
        set { _part = value; _systemAccess.NotifyAdded(_part); }
    }
}

public class NodePart {
    void NotifyAdded() { Console.WriteLine("Part added"); }

    internal interface ISystemAccess {
        void NotifyAdded(NodePart part);
    }

    class SystemAccess : ISystemAccess {
        void ISystemAccess.NotifyAdded(NodePart part) {
            part.NotifyAdded();
        }
    }
}
Hacka hacka hacka。我的原始版本没有reflect + invoke,并且依赖于SystemAccess是不明显的。这可能也可以,但我有点像我在这里获得的额外安全性,即使它可能没有必要。

6 个答案:

答案 0 :(得分:3)

使NodePart成为Node中的嵌套类,它将能够访问其所有私有成员。

如果那不可能(不想要using Node?),那么你可以尝试部分类。我不知道那会怎么样,因为我从来没有真正以更高级的方式使用它们。

答案 1 :(得分:2)

friend assemblies你想要做什么?他们自2.0以来一直在框架中,但我认为它没有得到很好的宣传。

朋友程序集允许您从程序集A访问程序集B中的内部方法,但仍然隐藏了受保护和私有成员。

不确定这是否会有所帮助,但可以认为。

哦,唯一的缺点是你必须强烈命名那些组件。

答案 2 :(得分:2)

我能想到使用接口的唯一方法是显式地实现NodePart的字段,如果他们希望访问它们,则需要将任何派生类转发到接口。

interface INodePart {
    T SomeValue { get; }
}

class NodePart : INodePart {
    T INodePart.SomeValue { get; private set; }
}

class Node {
    void AddNodePart(NodePart np) {
        T val = (np as INodePart).SomeValue; //require downcast here.
        ...
    }
}

请注意,如果您希望访问Somevalue,则需要在NodePart内进行向下转发。

这显然不是万无一失的 - 任何人如果愿意都可以贬低,但这肯定是一种沮丧。

要添加到灰心,您可以使用[Obsolete]属性标记接口,并将NodePart和Node类包装在禁用的警告块中,这意味着任何其他尝试使用INodePart的人都会收到警告,但你的代码不会。

#pragma warning disable 612
class NodePart : INodePart { ... }
#pragma warning restore 612

您可以对此进行严格处理,并将/warnaserror:612添加到构建参数中,但如果依赖于其他地方标记为Obsolete的任何其他代码,它将会破坏您的代码。 (虽然您可以为某些文件启用/禁用此功能,但可能需要手动黑客攻击.csproj文件)

答案 3 :(得分:1)

答案 4 :(得分:0)

我相信你实际上可以在内部创建一个接口,所以你需要的行为可以封装在项目的主程序集中,但其他任何东西都看不到行为?

答案 5 :(得分:-1)

如果你真的想,你可以使用这样的属性,即友元类存储加密哈希,无论“Friended”类可以在运行时使用反射访问朋友类的代码,并且有一个每次调用此哈希时检查此哈希的方法的公共版本。我没有试过这个,并且我不确定反射会给你任何适合哈希的东西,就像朋友类的二进制位一样。

或者您可以以类似的方式使用.NET安全性。

只是头脑风暴,如果你能避免它,这些就不值得。