C# 等同于C++好友关键字
本文关键字:关键字 好友 C++ 等同于 | 更新日期: 2023-09-27 18:33:38
我是C#的新手,我有一个问题,在C++中我通常会使用friend
标识符。现在我知道 friend
关键字在 C# 中不存在,但我没有任何解决此问题的经验(除了将所有类变量设为公共属性,如果可以的话,我想避免这种情况(。
我有以下情况:
public class A
{
public string Info { get; set; }
/* much more data */
}
public class B
{
private A m_instanceOfA;
public B(A a) { m_instanceOfA = a; }
public Info { get return A.info; set A.Info = value; }
/* And some more data of its own*/
}
public class C
{
private A m_instanceOfA;
// I need a constructor of C, which needs to set C.m_instanceOfA
// to the same value as b.m_instanceOfA.
public C(B b) { m_instanceOfA = b.m_instanceOfA ; } // <--- Not allowed!
/* And some more data of its own*/
}
有没有其他聪明的方法,在不公开B.m_instanceOfA
的情况下,让C
访问这个变量(仅在构造函数中(?
下面显示的技巧在 Bob 上创建一个只能由 Alice
调用的方法FriendRecieveMessageFromAlice
。Eve
,一个邪恶的类如果不对私有成员使用反射,就无法调用该方法。
我很想知道其他人以前是否提出过这种或其他解决方案。几个月来,我一直在寻找这个问题的解决方案,但我从未见过一个确保真实friend
语义的解决方案,前提是不使用反射(你可以用它绕过几乎任何东西(。
爱丽丝和鲍勃
public interface IKey { }
public class Alice
{
// Alice, Bob and Carol must only have private constructors, so only nested classes can subclass them.
private Alice() { }
public static Alice Create() { return new Alice(); }
private class AlicePrivateKey : Alice, IKey { }
public void PublicSendMessageToBob() {
Bob.Create().FriendRecieveMessageFromAlice<AlicePrivateKey>(42);
}
public void FriendRecieveMessageFromBob<TKey>(int message) where TKey : Bob, IKey {
System.Console.WriteLine("Alice: I recieved message {0} from my friend Bob.", message);
}
}
public class Bob
{
private Bob() { }
public static Bob Create() { return new Bob(); }
private class BobPrivateKey : Bob, IKey { }
public void PublicSendMessageToAlice() {
Alice.Create().FriendRecieveMessageFromBob<BobPrivateKey>(1337);
}
public void FriendRecieveMessageFromAlice<TKey>(int message) where TKey : Alice, IKey {
System.Console.WriteLine("Bob: I recieved message {0} from my friend Alice.", message);
}
}
class Program
{
static void Main(string[] args) {
Alice.Create().PublicSendMessageToBob();
Bob.Create().PublicSendMessageToAlice();
}
}
前夕
public class Eve
{
// Eve can't write that, it won't compile:
// 'Alice.Alice()' is inaccessible due to its protection level
private class EvePrivateKey : Alice, IKey { }
public void PublicSendMesssageToBob() {
// Eve can't write that either:
// 'Alice.AlicePrivateKey' is inaccessible due to its protection level
Bob.Create().FriendRecieveMessageFromAlice<Alice.AlicePrivateKey>(42);
}
}
工作原理
诀窍是方法Bob.FriendRecieveMessageFromAlice
需要一个用作标记的(虚拟(泛型类型参数。该泛型类型必须继承自Alice
和虚拟接口IKey
。
由于Alice
本身没有实现IKey
,因此调用方需要提供一些Alice
子类来实现IKey
。但是,Alice
只有私有构造函数,因此它只能由嵌套类子类化,而不能由在其他地方声明的类子类化。
这意味着只有嵌套在Alice
中的类才能对其进行子类化以实现IKey
。这就是AlicePrivateKey
所做的,由于它是私有的,只有Alice
可以将其作为泛型参数传递给Bob.FriendRecieveMessageFromAlice
,所以只有Alice
可以调用该方法。
然后,我们反过来做同样的事情,这样只有Bob
才能调用Alice.FriendRecieveMessageFromBob
。
泄露密钥
值得注意的是,在调用时,Bob.FriendRecieveMessageFromAlice
可以访问TKey
泛型类型参数,并且可以使用它来欺骗另一个方法上的Alice
调用,OtherClass.OtherMethod<OtherTkey>
接受OtherTKey : Alice, IKey
。因此,使密钥从不同的接口继承会更安全:第一个接口Alice, IBobKey
,第二个接口Alice, IOtherKey
。
比C++朋友好
- 即使
Bob
本身也不能将自己的方法称为Bob.FriendRecieveMessageFromAlice
。 Bob 可以使用不同的好友方法拥有多个好友:
// Can only be called by Alice, not by Carol or Bob itself Bob.FriendRecieveMessageFromAlice <TKey>(int message) where TKey : Alice, IKey { } // Can only be called by Carol, not by Alice or Bob itself Bob.FriendRecieveMessageFromCarol <TKey>(int message) where TKey : Carol, IKey { }
我很想知道是否有某种方法可以比暴力试验和错误更有效的方式找到这样的技巧。某种"C#类型系统的代数",它告诉我们哪些限制可以强制执行,哪些限制不能强制执行,但我还没有看到任何关于这种主题的讨论。
内部
您可以使用内部关键字。然后,您的类型(或类型成员(将仅对同一程序集中的其他类型可见;还有:
如果需要内部类型对其他程序集可见,则可以使用 InternalsVisibleToAttribute。此属性面向整个程序集,通常写入 AssemblyInfo.cs 文件中。
PS:Friend 关键字在 C# 中不存在,但存在友谊的概念(与 C++ 中的概念不完全相同(,MSDN 的"朋友程序集"文章中对此进行了描述。另请注意,友元关键字存在于 VB.NET 中,其行为与 C# 内部关键字完全相同。
您只能使用 5 个辅助功能修饰符:
公共访问不受限制。
受保护的访问仅限于包含类或从包含类派生的类型。
内部访问仅限于当前程序集。
受保护的内部访问仅限于当前程序集或从包含类派生的类型。
私人访问仅限于包含类型。
我修改了您发布的代码,因此它应该完全按照您想要的方式工作:
using System.Reflection;
using System.Diagnostics;
public class A
{
public string Info { get; set; }
/* much more data */
}
public class B
{
private A m_instanceOfA;
public string Info { get; set; }
public B(A a) => Info = a;
private readonly ConstructorInfo friend = typeof(C).GetConstructor(new Type[] { typeof(B) });
public A InstanceOfA
{
get
{
if (new StackFrame(1).GetMethod() != friend)
throw new Exception("Call this property only inside the constructor of C");
return this.m_instanceOfA;
}
}
}
public class C
{
private A m_instanceOfA;
// Only the constructor of C can set his m_instanceOfA
// to the same value as b.m_instanceOfA.
public C(B b)
{
Info = b.InstanceOfA; // Call the public property, not the private field. Now it is allowed and it will work too, because you call it inside the constructor of C. In Main method, for example, an exception will be thrown, if you try to get InstanceOfA there.
}
}
我认为您正在寻找"internal"关键字 - 基本上仅对同一程序集中的类可见
或者,您可以这样(请原谅方法名称!
public interface IAmAFriendOfB {
void DoSomethingWithA(A instanceOfA);
}
public class B {
private A m_instanceOfA;
public B(A a) { m_instanceOfA = a; }
public void BeFriendlyWith(IAmAFriendOfB friend) {
friend.DoSomethingWithA(m_instanceOfA);
}
// the rest of your class
}
public class C : IAmAFriendOfB {
private A m_instanceOfA;
public C(B b) {
b.BeFriendlyWith(this);
}
void DoSomethingWithA(A instanceOfA) {
m_instanceOfA = b.m_instanceOfA;
}
}
这是使用具有private
单一实例的internal
类的另一种替代方法,它允许您微调向伪friend
类公开的方法。
using System;
namespace Test
{
public class A
{
public string Info { get; set; }
/* much more data */
}
public class B
{
private A m_instanceOfA;
public B(A a) { m_instanceOfA = a; }
public string Info
{
get { return m_instanceOfA.Info; }
set { m_instanceOfA.Info = value; }
}
// requires an instance of a private object, this establishes our pseudo-friendship
internal A GetInstanceOfA(C.AGetter getter) { return getter.Get(m_instanceOfA); }
/* And some more data of its own*/
}
public class C
{
private A m_instanceOfA;
private static AGetter m_AGetter; // initialized before first use; not visible outside of C
// class needs to be visible to B, actual instance does not (we call b.GetInstanceOfA from C)
internal class AGetter
{
static AGetter() { m_AGetter = new AGetter(); } // initialize singleton
private AGetter() { } // disallow instantiation except our private singleton in C
public A Get(A a) { return a; } // force a NullReferenceException if calling b.GetInstanceOfA(null)
}
static C()
{
// ensure that m_AGetter is initialized
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(AGetter).TypeHandle);
}
public C(B b)
{
m_instanceOfA = b.GetInstanceOfA(m_AGetter);
}
public string Info
{
get { return m_instanceOfA.Info; }
set { m_instanceOfA.Info = value; }
}
/* And some more data of its own*/
}
public class Test
{
public static void Main()
{
A a = new A();
B b = new B(a);
C c = new C(b);
c.Info = "Hello World!";
Console.WriteLine(a.Info);
}
}
}
现场演示
C.AGetter
类不能在自身外部实例化,因此C.m_AGetter
(既private
又static
(表示只能从C
内部访问的单例实例。由于B.GetInstanceOfA
需要一个C.AGetter
的实例,这使得函数在C
之外毫无用处。该函数被标记为internal
以尽量减少其暴露,但该参数也应充当一种自我文档形式,它不适合常用。
替代方法可能会暴露超出其预期范围的方法(例如,实现接口的类,它不应该访问公开的方法(,而这种方法可以防止这种情况。friend
访问的反对者可能仍然反对它,但这会使事情更接近预期范围。