【问题标题】:How to emulate friendship in C#?如何在 C# 中模拟友谊?
【发布时间】:2015-12-11 17:14:08
【问题描述】:

我想声明(仅)A 类可以访问/操作 B 类的(部分)成员。当C# 中不存在friend 时,如何实现这一点?

【问题讨论】:

  • 我希望有人机界面标签 =)。更严重的是,这个机制不是故意跳过的,因为它通常表明或导致糟糕的设计吗?我认为关闭和最合适的方法是定义内部类。
  • 关于friendship 的好与坏有一些很长的讨论。我发现它很有用,并且会犹豫称它为坏。无论如何,整个文本作为我需要实施以实现客户要求的模式的最终定稿提出,我认为它可能对某人有所帮助。
  • 您有什么理由不喜欢internal
  • 在我看来internal 太笼统了。据我所知,同一个程序集中的任何类都可以访问任何内部。
  • @sharpener:是的,而且你知道要参与这个程序集的每个人的名字。您可以通过要求这些人远离 B 类来解决问题,并为他们提供一个以业务为中心的充分理由,让他们这样做。

标签: c# friend


【解决方案1】:

我想声明只有类A可以访问/操作一些类B的成员。

突出显示的单词表示有问题的点。

首先,我假设“仅 A 类”实际上是指“仅 A 类和 B 类”。在 B 类中声明的成员中的代码始终可以访问 B 类的所有成员,因此 A 类将永远唯一可以访问 B 成员的类。

一种技术是让 A 类成为 B 类的唯一嵌套类。现在只有 A 类和 B 类可以访问 B 类的所有成员。但是,A 类可以访问所有 B类成员;这不符合您的要求,即 A 类只能访问 B 类的一些成员。

另一种技术是将 A 类和 B 类都放在同一个程序集中,而 only 类在程序集中。现在您可以将希望 A 访问的 B 成员标记为“内部”,并且只有 A 类能够访问它们,因为它是程序集中唯一的其他类。

很可能这些技术都不是您想要的。正如您所指出的,C# 的设计初衷并非具有 C++ 的“朋友”特性。我的建议是使您希望从 A 访问的 B 成员成为内部成员。这使得它们可以被当前程序集中的所有类型访问,这是由少数人编写的少数类型,所有这些人都是你认识的。如果您有一些很好的、影响业务的理由来强加此要求,您可以简单地要求他们不要使用 B 类。

C# 类型系统并非设计为能够表示和强制执行同事之间所有可能的关系; “B 类可以被 A 类使用,因为我信任 Albert,但我不希望从事 C 类工作的 Carol 能够重用我的工作”,这根本不是 C# 团队认为有价值的功能添加到类型系统。如果您对 Carol 使用 B 类有疑虑,请与 Carol 交谈。

【讨论】:

  • 有趣的是,可以通过friend-like 功能解决的问题通过实际与人交谈来解决:)
  • @Eric Lippert:首先,是的,需求的制定并不是 100% 精确的。我还应该提到我需要B 对程序集中的其他人可见。下一篇:我不怪C#没有friend(你听起来有点防御)。我只是为与我有相似要求的人提供我的幼稚方法。
  • 嗨埃里克..我们不能让 A 类抽象,然后让 B 的密封类继承它..在这种情况下,只有 A 的受保护方法(两个类已经同意)可以只能由 B 访问,并且其他(私有)方法将保持隐藏状态。
  • @kyle:首先,你似乎把课程倒退了。问题是类 A 是否可以是 B 方法的唯一用户?你的提议表明B类可以是A方法的用户。其次,我不明白你的提议。是什么阻止了第三类的存在,它也访问了受保护的成员?第三,“朋友”类之间很少有“是一种”的关系;事实上,这种机制在 C++ 中的意义在于共享功能没有继承。
【解决方案2】:

以下是一种可能性(希望它可以为某人节省一些思考)。这适用于那些:

  • 不喜欢(不想使用)internalInternalsVisibleToAttribute
  • 不喜欢(不想使用)反射来提取和利用“内部”。

说明:

  • AB 类处理一些允许A 操作B 的“内部”的协议(接口)。
  • B 定义实现此协议的非公共方法(类似于标准接口实现,但使用private/protected 方法 - 禁止标准使用interface 实现)。
  • B 创建一个专用对象,其中包含初始化为本协议成员的委托(有效导出选定的“内部”)。
  • 将此对象从B 传递给A(理想情况下不需要任何用户干预)以授予A 操作B 的权利。
  • A 中使用它可以有效地允许B 愿意提供的任何东西。

优点:

  • B 中的任何意外更改的空间更少(与此相反,所有情况都是internalpublic)。
  • 精确控制可以从AB 执行的操作。

缺点:

  • 稍微复杂一点(协议定义、协议传递过程的需要)。
  • A 仍然提供公共基础设施方法(注册),如果调用不当,可能会造成一些危害。

代码搜索器示例:

  • A 是会修手机的技术人员
  • B 是一款智能手机,它提供标准 (public) 功能以及一些基本维修技术。
  • 用户可以使用标准功能,Technician(作为SmartPhone's friend)可以使用高级功能。

    /*
     * SmartPhone<-Technician friendship contract
     */
    public class SmartPhoneServiceActivities
    {
        public delegate bool ReplaceGlassHandler  (SmartPhone device);
        public delegate bool ReplaceDisplayHandler(SmartPhone device);
    
        public ReplaceGlassHandler   ReplaceGlass {get;}
        public ReplaceDisplayHandler FactoryReset {get;}
    
        public SmartPhoneServiceActivities(ReplaceGlassHandler replaceGlass, ReplaceDisplayHandler factoryReset)
        {
            ReplaceGlass = replaceGlass;
            FactoryReset = factoryReset;
        }
    }
    
    //-------------------------------------------------------------------------------
    
    public class SmartPhone
    {
        #region Friend functions
        /*
         * Provide private functions for "trusted" classes
         */
        static SmartPhone()
        {
            var services = new SmartPhoneServiceActivities(ReplaceGlass, FactoryReset); // SmartPhone<-Technician agreement regarding "private modifications"
    
            Technician.RegisterDeviceForRepair(typeof(SmartPhone), services);
        }
    
        protected static bool ReplaceGlass(SmartPhone device)
        {
            device.IsDamagedGlass = false;
            return true; // Skilled technicians only ;)
        }
    
        protected static bool FactoryReset(SmartPhone device)
        {
            device.IsDamagedSoftware = false;
            return true;
        }
        #endregion
    
    
    
        #region Standard public usage
        public bool IsDamaged => IsDamagedGlass || IsDamagedSoftware;
        public bool IsDamagedGlass    {get; protected set;}
        public bool IsDamagedSoftware {get; protected set;}
    
        public void Call() {}
    
        public void DropIt()
        {
            var isBroken = (new Random((int)(DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds)).Next() & 3;
    
            IsDamagedGlass    = (isBroken & 1) != 0;
            IsDamagedSoftware = (isBroken & 2) != 0;
        }
        #endregion
    }
    
    // --------------------------------------------------------------------------------
    
    public class Technician
    {
        protected static readonly Dictionary<Type, SmartPhoneServiceActivities> knowHow = new Dictionary<Type, SmartPhoneServiceActivities>();
    
        /*
         * Although this is public and improper call might cause inability of a class to register it's acceptable "risk". 
         * It's intended for static constructors invocation.
         */
        public static void RegisterDeviceForRepair(Type deviceType, SmartPhoneServiceActivities services)
        {
            if (   (!knowHow.ContainsKey(deviceType))
                && (services != null))
            {
                knowHow[deviceType] = services;
            }
        }
    
        public bool Repair(SmartPhone device)
        {
            var isRepaired = false;
            var deviceType = device.GetType();
    
            if (knowHow.ContainsKey(deviceType))
            {
                var services  = knowHow[deviceType];
                var isSoftOk  = !device.IsDamagedSoftware || services.FactoryReset(device);
                var isGlassOk = !device.IsDamagedGlass    || services.ReplaceGlass(device);
    
                isRepaired = isSoftOk && isGlassOk;
            }
    
            return isRepaired; 
        }
    }
    

【讨论】:

  • 这种模式是为了防止粗心/无能,还是应该防止恶意?对于粗心/无能:您能否确定您的静态构造函数将在我的 IdiotPhone 的构造函数之前运行,该构造函数意外注册了 Smartphone 类型?在恶意的情况下:假设我是一个EvilTechnician,一心想要统治世界。有什么能阻止我只是用反思抓起字典,清理它,把自己装成维修霸主,把维修成本翻倍?
  • @theB:第一个。这远非理想,这是我能想到的最好的(这也是我试图从这里得到一些改进的原因)。
猜你喜欢
  • 1970-01-01
  • 2021-10-30
  • 2012-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-10
  • 2014-09-30
相关资源
最近更新 更多