8

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

我有以下情况:

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

4

6 回答 6

16

您可以使用下面显示的技巧在 Bob 上创建一个FriendRecieveMessageFromAlice只能由 调用的方法Alice。一个邪恶的类,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调用该方法。

然后我们反过来做同样的事情,这样只有Bobcall 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# 类型系统的代数”,它告诉我们哪些限制可以强制执行,哪些不能,但我还没有看到任何关于此类主题的讨论。

于 2014-02-03T00:17:06.583 回答
9

内部的

您可以使用internal关键字。然后,您的类型(或类型成员)将仅对同一程序集中的其他类型可见;并且:

如果您需要从其他程序集中看到您的内部类型,您可以使用InternalsVisibleToAttribute。此属性针对您的整个程序集,通常写在AssemblyInfo.cs文件中。


PS: C# 中不存在 Friend 关键字,但存在友谊的概念(与 C++ 中的不完全相同),在 MSDN的Friend Assemblies文章中有所描述。另请注意,在 VB.NET 中存在一个friend 关键字,其行为与 C# internal 关键字完全相同。

于 2013-10-02T11:39:32.243 回答
2

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

公共访问不受限制。

protected访问仅限于包含类或从包含类派生的类型。

internal访问仅限于当前程序集。

protected internal 访问仅限于当前程序集或从包含类派生的类型。

private 访问仅限于包含类型。

于 2013-10-02T11:18:15.283 回答
2

我修改了您发布的代码,因此它应该可以完全按照您的要求工作:

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.
    }
}
于 2014-09-22T12:34:05.990 回答
1

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

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

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;
    }   
}
于 2013-10-02T11:10:25.490 回答
1

internal这是使用带有单例实例的类的另一种选择private,它允许您微调哪些方法暴露给伪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可能仍然反对它,但这使事情更接近预期的范围。

于 2017-07-13T14:02:57.140 回答