【问题标题】:Can There Be Private Extension Methods?可以有私有扩展方法吗?
【发布时间】:2013-05-20 08:18:33
【问题描述】:

假设我需要一个简单的私有辅助方法,并且直观地在代码中将其作为扩展方法是有意义的。有没有办法将该助手封装到唯一真正需要使用它的类中?

例如,我试试这个:

class Program
{
    static void Main(string[] args)
    {
        var value = 0;
        value = value.GetNext(); // Compiler error
    }

    static int GetNext(this int i)
    {
        return i + 1;
    }
}

编译器不会“看到”GetNext() 扩展方法。错误是:

扩展方法必须定义在非泛型静态类中

很公平,所以我将它包装在它自己的类中,但仍然封装在它所属的对象中:

class Program
{
    static void Main(string[] args)
    {
        var value = 0;
        value = value.GetNext(); // Compiler error
    }

    static class Extensions
    {
        static int GetNext(this int i)
        {
            return i + 1;
        }
    }
}

仍然没有骰子。现在错误状态:

扩展方法必须定义在顶级静态类中; Extensions 是一个嵌套类。

此要求是否有令人信服的理由?在某些情况下,辅助方法确实应该被私有封装,并且如果辅助方法是扩展方法,则在某些情况下代码会更清晰、更易读/可支持。对于这两个相交的情况,是否都可以满足,还是必须选择一个而不是另一个?

【问题讨论】:

  • 阅读第一条错误消息的内容,它希望您的程序类是静态的,您是否尝试将其设为静态并尝试您的代码?
  • @Bearcat9425,这将“修复”这个特定的用例场景,但不是普遍适用的。我想这个问题是为了普遍适用,而不是针对示例控制台应用程序。
  • 我怀疑你能得到的最好的方法是内部静态类/内部扩展方法并将其隐藏在一些不起眼的命名空间中......
  • @AlexeiLevenkov:你可能是对的,而且在大多数情况下,我只是巧合而已。似乎我的大多数“私人”助手都在抽象(依赖注入)程序集中,其内部甚至对域的其余部分都不可见,并且很容易使扩展成为顶级那个案子。直到最近才出现。

标签: c# extension-methods encapsulation


【解决方案1】:

此要求是否有令人信服的理由?

这是一个错误的问题。当我们设计此功能时,语言设计团队提出的问题是:

是否有令人信服的理由允许在嵌套静态类型中声明扩展方法?

由于扩展方法旨在使 LINQ 工作,并且 LINQ 不存在扩展方法对类型私有的情况,因此答案是“不,没有这样令人信服的理由”。

通过消除将扩展方法放入静态嵌套类型的能力,无需考虑、争论、设计、指定、实现、测试、记录、交付到静态嵌套类型中搜索扩展方法的规则客户,或与 C# 的所有未来功能兼容。这大大节省了成本。

【讨论】:

  • "searching for extension methods" - 我没有想到的部分就在那里。在看台后面大喊“这应该有效”很容易,而忘记了让它发挥作用所需的努力。现在完全有道理,并结合阿列克谢的答案得出了一个非常可行的解决方案。谢谢!
  • 这告诉我的是,设计时并没有真正设计扩展方法本身,而是他们设计了 LINQ。扩展方法为私有的用例对于所有其他私有方法都是相同的。扩展方法没有什么特别之处。
  • 理论上这是正确的答案。设计团队必须为代码可以做什么设定界限以使其易于管理。
  • 我认为这是最好的答案。但是,我希望凭借 Roslyn 的敏捷性,有朝一日可以重新考虑这一点,尤其是对于本地功能。扩展方法解决的不仅仅是 LINQ——它们允许以传统函数调用所不具备的方式“流畅”地自我记录数据管道。我个人喜欢类型推断的 lambdas 和表达式 lambdas ......范围内的小表达小码。 C# 团队表示,本地函数是类型推断 lambda(委托歧义等)的选择替代方案,因此这是我对放宽此限制的投资的投票。
【解决方案2】:

我相信在一般情况下你能得到的最好的就是internal static 类和internal static 扩展方法。由于它将在您自己的程序集中,因此您需要阻止使用扩展的唯一人员是程序集的作者 - 所以一些明确命名的命名空间(如 My.Extensions.ForFoobarOnly)可能足以提示避免误用。

implement extension 文章中涵盖的最小internal 限制

该类必须对客户端代码可见...方法至少与包含的类具有相同的可见性。

注意:无论如何,我都会将扩展公开以简化单元测试,但会放入一些明确命名的命名空间,例如 Xxxx.Yyyy.Internal,因此程序集的其他用户不会期望这些方法受支持/可调用。本质上依赖于编译时强制以外的约定。

【讨论】:

  • 我可能会在很长一段时间内都在使用这些约定,最终得出一些有意义的东西。这是一个非常罕见的情况,我并不十分担心,这只是让代码库处于直观且可支持的状态,以供将来必须支持它的人(包括我自己)使用。谢谢!
  • Alexei:如果只是为了单元测试,将扩展保留在内部并使用 InternalsVisibleToAttribute (msdn.microsoft.com/en-us/library/…) 将内部暴露给您的测试项目不是更好吗?
  • @Przemo InternalsVisibleToAttribute 确实可以使用,但它会破坏扩展方法的一些可用性,因为据我所知,它们将不再出现在自动完成中。对于大多数项目来说,这或多或少是个人选择(除了一些专为广泛使用而设计的库)——您需要在特定情况下权衡 API 的便利性与纯度。我从事的项目通常“小”并且不公开真正的公共 API - 所以public 在我的情况下工作正常。
【解决方案3】:

此代码编译并工作:

static class Program
{
    static void Main(string[] args)
    {
        var value = 0;
        value = value.GetNext(); // Compiler error
    }

    static int GetNext(this int i)
    {
        return i + 1;
    }
}

注意static class Program 行,这是编译器所说的需要。

【讨论】:

  • 这在我设计的例子中运行良好,我承认。但是使类静态化并不总是一个可行的选择。对于域模型,它可能会导致大问题。
  • 这就是为什么它们被称为“扩展方法”,最佳实践是将它们定义在一个单独的静态类中,放置在不同的命名空间下。
  • “最佳实践”是一个相对术语。封装也是一种最佳实践。在某些情况下,将辅助函数(在本例中为扩展方法)放在单独的类(或完全独立的命名空间)中会导致抽象泄漏,从而使辅助函数与它所帮助的唯一对象的内部紧密耦合。跨度>
  • 我不了解您的域内部情况,您很可能是对的。但是,如果它们只是一些辅助函数,那么它们可能不应该是扩展方法。扩展方法旨在提供鸭子类型的静态类型形式,而这种形式又需要实现某种高阶类型系统,例如 LINQ(绑定到 IEnumerable 的 Monad)。它们与封装无关。但是(再次)您可以根据自己的意愿塑造它。所以“最佳实践”部分是我自己从别人那里学到的,并且在实践中帮助了我;而且是相对的!
【解决方案4】:

我相信这是他们实现扩展方法编译的方式。

查看 IL,似乎它们为方法添加了一些额外的属性。

.method public hidebysig static int32 GetNext(int32 i) cil managed
{
    .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
    .maxstack 2
    .locals init (
        [0] int32 num)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldc.i4.1 
    L_0003: add 
    L_0004: dup 
    L_0005: starg.s i
    L_0007: stloc.0 
    L_0008: br.s L_000a
    L_000a: ldloc.0 
    L_000b: ret 
}

我们可能遗漏了一些非常基本的东西,只是不能让它发挥作用,这就是限制到位的原因。也可能只是他们想强制编码实践。不幸的是,它不起作用,必须在顶级静态类中。

【讨论】:

    【解决方案5】:

    虽然Alexei Levenkov 正确回答了这个问题,但我想添加一个简单的例子。 由于扩展方法 - 如果它们是私有的 - 没有多大意义,因为在包含扩展类之外无法访问,您可以将它们放在自定义命名空间中,并仅在您自己(或任何其他)命名空间中使用该命名空间,这使得全局无法访问的扩展方法。

    namespace YourOwnNameSpace
    {
        using YourExtensionNameSpace;
    
        static class YourClass
        {
            public static void Test()
            {
                Console.WriteLine("Blah".Bracketize());
            }
        }
    }
    
    namespace YourOwnNameSpace
    {
        namespace YourExtensionNameSpace
        {
            static class YourPrivateExtensions
            {
                public static string Bracketize(this string src)
                {
                    return "{[(" + src + ")]}";
                }
            }
        }
    }
    

    你必须定义你的命名空间两次,并将你的扩展命名空间嵌套在另一个命名空间中,而不是你的类所在的位置,你必须在using 使用它。像这样扩展方法在你不是using的地方是不可见的。

    【讨论】:

      【解决方案6】:

      对于任何来到这里可能正在寻找实际答案的人来说,这是YES

      您可以使用私有扩展方法,下面是我正在处理的 Unity 项目中使用的示例:

      [Serializable]
      public class IMovableProps
      {
          public Vector3 gravity = new Vector3(0f, -9.81f, 0f);
          public bool isWeebleWobble = true;
      }
      
      public interface IMovable
      {
          public Transform transform { get; }
          public IMovableProps movableProps { get; set; }
      }
      
      public static class IMovableExtension
      {
          public static void SetGravity(this IMovable movable, Vector3 gravity)
          {
              movable.movableProps.gravity = gravity;
              movable.WeebleWobble();
          }
      
          private static void WeebleWobble(this IMovable movable)
          {
              //* Get the Props object
              IMovableProps props = movable.movableProps;
              //* Align the Transform's up to be against gravity if isWeebleWobble is true
              if (props.isWeebleWobble)
              {
                  movable.transform.up = props.gravity * -1;
              }
          }
      }
      

      我显然精简了很多,但要点是它可以按预期编译和运行,这是有道理的,因为我希望扩展方法可以访问 WeebleWobble 功能,但我不希望它暴露在任何地方在静态类之外。我可以很好地将WeebleWobble 配置为在IMovableProps 类中工作,但这只是为了证明这是可能的!

      编辑:如果您有 Unity 并想在此处进行测试,请使用 MonoBehaviour 进行测试(这显然不会实际应用重力,但当您在游戏期间检查检查器中的 inverseGravity 框时,它应该会改变旋转模式)

      public class WeebleWobbleTest : MonoBehaviour, IMovable
      {
          public bool inverseGravity;
          private IMovableProps _movableProps;
          public IMovableProps movableProps { get => _movableProps; set => _movableProps = value; }
      
          void Update()
          {
              if (inverseGravity)
              {
                  this.SetGravity(new Vector3(0f, 9.81f, 0f);
              }
              else
              {
                  this.SetGravity(new Vector3(0f, -9.81f, 0f);
              }
          }
      }
      

      【讨论】:

      • 如果我没记错的话,扩展方法只能在静态类中定义,所以虽然这可行,但必须考虑到限制。
      【解决方案7】:

      这取自 microsoft msdn 上的示例。扩展方法必须在静态类中定义。查看静态类是如何在不同的命名空间中定义并导入的。您可以在此处查看示例http://msdn.microsoft.com/en-us/library/bb311042(v=vs.90).aspx

      namespace TestingEXtensions
      {
          using CustomExtensions;
          class Program
          {
              static void Main(string[] args)
              {
                  var value = 0;
                  Console.WriteLine(value.ToString()); //Test output
                  value = value.GetNext(); 
                  Console.WriteLine(value.ToString()); // see that it incremented
                  Console.ReadLine();
              }
          }
      }
      
      namespace CustomExtensions 
      {
          public static class IntExtensions
          {
              public static int GetNext(this int i)
              {
                  return i + 1;
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-05
        • 1970-01-01
        • 2013-01-06
        • 2011-11-21
        • 2021-12-25
        相关资源
        最近更新 更多