可以有私人扩展方法吗?

时间:2013-05-24 16:47:25

标签: c# extension-methods encapsulation

让我们说我需要一个简单的私有助手方法,并且直观地在代码中它作为扩展方法是有意义的。有没有办法将该帮助器封装到实际需要使用它的唯一类中?

例如,我试试这个:

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

仍然没有骰子。现在错误说明:

  

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

这个要求有令人信服的理由吗?在某些情况下,辅助方法确实应该是私有封装的,并且如果辅助方法是扩展方法,则存在代码更清晰且更易读/可支持的情况。对于这两者相交的情况,既可以满足,也可以选择其中一种吗?

7 个答案:

答案 0 :(得分:41)

  

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

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

  

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

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

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

答案 1 :(得分:19)

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

implement extension文章

中涵盖的最小internal限制
  

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

注意:我会将扩展公共设置为简化单元测试,但是放入一些明确命名的命名空间,如Xxxx.Yyyy.Internal,因此程序集的其他用户不会期望支持/调用这些方法。基本上依赖于编译时执行以外的约定。

答案 2 :(得分:1)

此代码编译并运行:

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行。

答案 3 :(得分:1)

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

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

我们可能缺少一些非常基本的东西,只是没有使它工作,这就是限制到位的原因。也可能只是因为他们想要强制编码实践。不幸的是,它只是不起作用,必须在顶级静态类中。

答案 4 :(得分:0)

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不在的地方不可见。

答案 5 :(得分:0)

对于来这里寻找实际答案的人来说,

您可以拥有私有扩展方法,以下是我在我目前正在处理的 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);
        }
    }
}

答案 6 :(得分:-1)

这取自microsoft msdn上的一个例子。 Extesnion方法必须在静态类中定义。了解Static类是如何在不同的命名空间中定义并导入的。您可以在此处查看示例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;
        }
    }
}