我需要在C#中实现一些C ++风格的“朋友”功能,并且正在寻找创意。
考虑两个密切相关的类:Node和NodePart。节点包含NodeParts,它通过公共AddPart()调用从其他系统添加,其中包含这些系统构建的部分。节点需要“进入”NodePart以执行一些非常具体的通知,NodePart将通过单独的虚拟保护方法将其分发到某些处理后的任何派生类。 (对于熟悉基于组件的游戏对象编程的人来说,这是同样的事情。)
我希望能够让NodePart为Node提供一种获取这些通知方法的方法,而不会让系统中的任何其他类型做到这一点。 Node无需访问任何其他NodePart内部,只需转发一些私人通知即可。
现在,把这些类放在一个程序集中并使用'internal'显然可以解决这个问题,但我正在寻找比这更好的模式。 (我真的不想为将来要做的每一组课程产生新的程序集。)
除了反射+调用,这是令人讨厌和脆弱的,你能想到什么其他模式来解决这个问题?我的狡猾的感觉告诉我接口是解决方案的一部分,但我想不出如何。
(注意:我通过默默无闻保证安全。这个系统不必100%防止滥用 - 足以阻止人们做他们不应该做的事。我们不建立除颤器此处。)
更新:以下许多答案需要多个程序集。正如我上面提到的,我真的很想避免这种情况。我不想为每个组件放置一个系统。我们有足够的东西,由于我们使用XAML,我无法沿着IL链接路线走下去。但无论如何,谢谢你的答案。 :)
更新2:我在Linqpad中搞砸了,根据下面的答案提出了几个选项。你喜欢哪个最差/最少?为什么?
#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(); }
}
}
不错。有点奇怪但奇怪的是非常狭窄。我倾向于这个,因为它比下一个选项更紧凑。
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是不明显的。这可能也可以,但我有点像我在这里获得的额外安全性,即使它可能没有必要。
答案 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)
InternalsVisibleto是您在C#
中最接近“朋友”的地方答案 4 :(得分:0)
我相信你实际上可以在内部创建一个接口,所以你需要的行为可以封装在项目的主程序集中,但其他任何东西都看不到行为?
答案 5 :(得分:-1)
如果你真的想,你可以使用这样的属性,即友元类存储加密哈希,无论“Friended”类可以在运行时使用反射访问朋友类的代码,并且有一个每次调用此哈希时检查此哈希的方法的公共版本。我没有试过这个,并且我不确定反射会给你任何适合哈希的东西,就像朋友类的二进制位一样。
或者您可以以类似的方式使用.NET安全性。
只是头脑风暴,如果你能避免它,这些就不值得。