关于oop:C#等同于C ++好友关键字?

C# equivalent to C++ friend keyword?

我对C是新的,我有一个问题,在C++中,我通常会使用EDCOX1×0的标识符。现在我知道c中不存在friend关键字,但是我没有任何关于如何解决这个问题的经验(除了将所有类变量设为公共属性,如果可以的话,我想避免)。

我有以下场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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上创建一个方法FriendRecieveMessageFromAlice,该方法只能由Alice调用。一个邪恶的类,Eve,如果不使用对私有成员的反射,就无法调用该方法。

我很想知道其他人以前是否提出过这种或另一种解决方案。几个月来,我一直在寻找解决这个问题的方法,但我从未见过一个能确保真正的friend语义的方法,前提是不使用反射(使用反射几乎可以绕过任何东西)。

爱丽丝和鲍伯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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();
    }
}

伊芙

1
2
3
4
5
6
7
8
9
10
11
12
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所做的,由于它被声明为private,因此只有Alice可以将它作为泛型参数传递给Bob.FriendRecieveMessageFromAlice,因此只有Alice可以调用该方法。

然后我们反过来做同样的事情,这样只有Bob才能称为Alice.FriendRecieveMessageFromBob

泄漏钥匙

值得注意的是,当调用时,Bob.FriendRecieveMessageFromAlice可以访问TKey泛型类型参数,并可以使用它来欺骗来自Alice的调用,调用另一个方法OtherClass.OtherMethod接受OtherTKey : Alice, IKey的调用。因此,让密钥从不同的接口继承会更安全:第一个接口继承Alice, IBobKey,第二个接口继承Alice, IOtherKey

胜过C++朋友

  • 即使是Bob本身也不能调用自己的方法Bob.FriendRecieveMessageFromAlice
  • Bob可以有多个不同朋友方法的朋友:

    1
    2
    3
    4
    // 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:友元关键字不存在于C语言中,但友谊的概念却存在(与C++中的不完全相同),它在MSDN上的友元程序集文章中进行了描述。还要注意,friend关键字存在于vb.net中,其行为与c internal关键字完全相同。


我修改了您发布的代码,因此它应该可以按您的需要准确地工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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.
    }
}


这里还有另一种选择,使用带有private单例的internal类,它允许您微调哪些方法暴露于伪friend类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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访问的反对者可能仍然反对它,但这使事情更接近于预期的范围。


只能使用5个可访问性修饰符:

公共访问不受限制。

受保护的访问仅限于包含类或从包含类派生的类型。

内部访问仅限于当前程序集。

内部受保护访问仅限于当前程序集或从包含类派生的类型。

私有的访问仅限于包含类型。


我认为您正在寻找"internal"关键字-基本上只对同一程序集中的类可见

或者您可以这样做(请原谅方法名!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
    }  
}