繁体   English   中英

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

[英]C# equivalent to C++ friend keyword?

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

我有以下场景:

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 ,因此调用者需要提供一些实现IKeyAlice子类。 但是, 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#类型系统的代数”,它告诉我们可以强制执行哪些限制,哪些不可以,但我没有看到任何关于这类主题的讨论。

内部

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

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


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

我修改了你发布的代码,所以它应该按照你想要的方式工作:

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.
    }
}

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

或者你也可以这样(原谅方法名称!):

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;
    }   
}

您只能使用5个辅助功能修改器:

公共访问不受限制。

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

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

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

private Access仅限于包含类型。

这是使用具有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_AGetterprivatestatic )表示只能从C访问的单例实例。 作为B.GetInstanceOfA需要的一个实例C.AGetter ,这使得外部的功能无用C 该函数标记为internal以最大限度地减少其暴露,但该论点也应作为一种自我文档的形式,它不适合常用。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM