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访问这个变量(仅在构造函数中(?

C# 等同于C++好友关键字

您可以使用

下面显示的技巧在 Bob 上创建一个只能由 Alice 调用的方法FriendRecieveMessageFromAliceEve,一个邪恶的类如果不对私有成员使用反射,就无法调用该方法。

我很想知道其他人以前是否提出过这种或其他解决方案。几个月来,我一直在寻找这个问题的解决方案,但我从未见过一个确保真实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(既privatestatic(表示只能从C内部访问的单例实例。由于B.GetInstanceOfA需要一个C.AGetter的实例,这使得函数在C之外毫无用处。该函数被标记为internal以尽量减少其暴露,但该参数也应充当一种自我文档形式,它不适合常用。

接口

替代方法可能会暴露超出其预期范围的方法(例如,实现接口的类,它不应该访问公开的方法(,而这种方法可以防止这种情况。friend访问的反对者可能仍然反对它,但这会使事情更接近预期范围。