【问题标题】:encapsulation and friendship in C#C#中的封装和友谊
【发布时间】:2011-04-30 11:37:10
【问题描述】:

我目前的项目中有一个特殊案例。

我有:

public class A
{
   // etc.
}

public class B
{
   // etc.

   private void HandleSomeEvent(object parameter)
   {
      // Etc.
   }

   protected void HandleSomeOtherEvent(object parameter)
   {
      // Etc.
   }
}

我想要:

  1. A 能够调用私有方法 B.HandleSomeEvent,但没有其他类(但 B)能够做到这一点
  2. A 能够调用受保护的方法 B.HandleSomeOtherEvent,但没有其他类(但 BB 的派生类)能够做到这一点

这在 C# 中可行吗?

  1. 如果可能,该怎么做?
  2. 如果不可能,有哪些替代方案可以尽可能保护 B 免受篡改,例如,同一程序集中的 C 类?

【问题讨论】:

    标签: c# encapsulation friend


    【解决方案1】:

    这在 C# 中可行吗?

    不,除非你使用反射:私有成员和受保护成员不能从其他类访问。​​

    如果不可能,有哪些替代方案可以尽可能保护 B 免受同一程序集中的 C 类篡改?

    您可以使 B 成为 A 中的嵌套类并将其设为私有。然后您可以安全地增加 B 的两种方法的可见性,因为只有 A 能够调用它们(除非使用反射)。

    【讨论】:

    • 我当前的解决方案是您自己的一个变体:我使 A 成为 B 内部的嵌套类,因此,A 可以“公开”访问 B 内部的所有方法,但这很痛苦,而且我还没有预见到所有的后果。
    【解决方案2】:

    这是可能的,但我通常会选择 internal 并假设程序集中的其他类表现良好。

    例如,您可以将这些方法的委托传递给 A 类,A 类将它们存储在 protected static 字段/属性中。

    【讨论】:

    • 假设其他类表现良好就是假设我是程序集的唯一维护者,并且我从不犯错误。这不是一个可行的解决方案(正如我在写作时指出的那样,我不希望同一个程序集中的 C 篡改 B)
    • 代表解决方案是一个有趣的解决方案。 +1
    【解决方案3】:

    “2. 如果不可能,有哪些替代方案可以尽可能保护 B 免受同一程序集中的 C 类篡改?”

    检查调用者的类型信息并抛出异常是否有效?

          [MethodImpl(MethodImplOptions.NoInlining)]  
          private void HandleSomeEvent(object parameter)  
          {  
                StackTrace stackTrace = new StackTrace();
                StackFrame stackFrame = stackTrace.GetFrame(1);
                MethodBase methodBase = stackFrame.GetMethod();
    
                if(methodBase.DeclaringType == typeof(ClassA)) // Okay.
                else if (methodBase.DeclaringType == typeof(ClassB)) // Okay.
                else throw new ApplicationException("Not Okay");
          }
    

    【讨论】:

    • +1。哇...这很复杂,但如果可行,它将至少提供运行时友谊,而无需附加任何条件。作为奖励,它可以实现功能级别的友谊。现在,我将不得不对其进行测试,因为“内联”部分困扰着我:如果 A 调用 B 而 B 又调用 C 怎么办。如果 B 是 C 的朋友,这没关系。但是如果 B 调用是内联的,这是否意味着堆栈,就运行时而言,在运行时被修改,因此,上面的代码将检测到“A 调用 C”而不是“A 调用 B 调用” C”?如果是,您的代码将不起作用。不过,这是一个有趣的解决方案。 :-)
    【解决方案4】:

    可以通过发出 A 的接口然后将其从具体实现中分离出来。一旦你做到了,你就可以写这样的东西

    interface IA { }
    
    class A : IA
    {
        public static IA New() { return new A(); }
        private A() { }
    
        private void UseB() 
        {  
            var b = new B();
            b.HandleSomeEvent(this, null);
        }
    }
    
    class B
    { 
        public void HandleSomeEvent(A onlyAccess, object parameter) { }
    }
    

    尽管HandleSomeEvent() 是公共的,但只有 A 可以访问它,因为其他人不可能获得 A 类的具体实例。它有一个私有构造函数,而工厂New() 方法返回一个接口。

    详情请看我的文章Friends and internal interface members at no cost with coding to interfaces

    【讨论】:

    • +1。一个有趣的 hack ......但它仍然是一个 hack:它太冗长了,它隐藏了一个复杂的设计背后的友谊。稍后我会详细阅读您的文章。
    • @paercebal 我不同意这是一个黑客行为。对接口进行编程不是黑客攻击,而是一种编码方式。既然你采用了它,你就几乎理所当然地得到了友谊。将接口与其实现分开一点也不冗长,这是一种很好的编程风格。顺便说一句,这就是我的文章的内容。
    • 这个例子是关于一个类的,但它应该是关于 C# 中的一个结构,通过其接口装箱结构的成本是否值得?我不这么认为。 . .另外,如果我调用 public B.HandleSomeEvent 方法传递“null”而不是 A 的实例怎么办?因此,充其量(如果 HandleSomeEvent 检查 A 不为空),我所拥有的是运行时友谊。 . .最后但并非最不重要的一点是,那里有一个约束:所有 A 构造函数都必须是私有的(或受保护的?),这可能或不可能。这就是为什么我认为这是一个 hack:它依赖于许多不相关的约束。
    • 作为替代方案,请查看以下答案:stackoverflow.com/a/5855379/14089。它使用反射,因此很丑陋。而且它只提供运行时友谊(就我而言,这仍然不够,但是,嗯......),但至少,不需要接口声明,也不需要私有构造函数。
    • @paercebal 我明白你的观点。关于空访问,你是对的。在我看来,保护构造函数是接口编码的一部分。但我不会再试图说服你,因为你的论点是合理的,而且是基于你自己的经验。
    【解决方案5】:

    您可以将这两种方法都标记为internal

    internal void HandleSomeEvent(object parameter)
    {
       // Etc.
    }
    
    protected internal void HandleSomeOtherEvent(object parameter)
    {
       // Etc.
    }
    

    这使得这两个方法对同一程序集中的所有类都可见(并且第二个方法对从B 派生的所有类都可见)。

    没有办法让方法对特定的其他类可见。毕竟,您是控制程序集中所有类的人,因此您需要确保除A 之外没有其他类调用这些方法。

    如果您在这方面确实需要工具的帮助,您可以编写 FxCop 规则或创建某种后期构建操作来检查来自 A 以外的其他类的方法调用。

    【讨论】:

    • 我的问题是,为了确保没有其他人会篡改 B,使用您的解决方案,我需要创建一个内部只有 A 和 B 的程序集。这不是一个可行的解决方案(这是我添加的,我不希望同一个程序集中的 C 篡改 B)
    • “你是控制你程序集中所有类的人”:不,我不是。我不是这个项目的唯一开发者。将其视为 40 名开发人员中的 30 名的国际团队。我的项目只是一个 DLL,有人可能会在接下来的几个月内编写代码。
    • “如果你真的需要工具的帮助,你可以写一个 FxCop 规则”:我需要的是 C# 编译器的帮助。使用外部工具禁止类访问 C# 编译器授权的公共或内部方法不是一个可行的解决方案。
    • @paercebal:C# 不适合您的需求。我想有一些技巧可以让它发挥作用(传递代表或不可伪造的令牌),但我认为它们很丑。我的建议:放弃对谁给你上课的控制权——让它发生。或者,如果后果真的那么糟糕,请重新考虑您的设计。
    【解决方案6】:
    1. 不,不是(除非您使用反射,但这只是糟糕的设计)
    2. 继承。但即便如此,您也无法调用私有方法。您应该考虑并改进您的设计。

    由于您的班级A 应该能够在B 上调用protected 方法,我认为它与它有某种关系。问问自己是否存在“is-a”关系,例如“A is a B”。在这种情况下,您可以让A 继承自B

    public class A : B
    {
    }
    

    现在,您可以在 B 上调用受保护的方法。但是请不要仅仅继承,要经常问自己继承是否有意义。

    【讨论】:

    • 您能否扩展继承解决方案(我不太关心private 方法)?可以提供代码吗?
    • 感谢代码,但没有:A 不是从 B 继承的。A 不是 B。
    • 那么,你应该考虑一下你的设计。
    • well, you should think about your design then :不,设计是正确的。 “C# 中没有朋友”的限制是一个限制。不多也不少。
    猜你喜欢
    • 2015-11-19
    • 1970-01-01
    • 2021-10-30
    • 1970-01-01
    • 1970-01-01
    • 2010-12-10
    • 2011-11-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多