可以有私有扩展方法吗?

本文关键字:方法 扩展 | 更新日期: 2023-09-27 18:15:08

假设我需要一个简单的私有帮助器方法,并且在代码中直观地将其作为扩展方法是有意义的。是否有任何方法将该帮助封装到实际需要使用它的唯一类?

例如,我尝试这样做:

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是一个嵌套类。

这个要求是否有令人信服的理由?在某些情况下,helper方法确实应该被单独封装,而在某些情况下,如果helper方法是扩展方法,则代码会更干净、更可读/更可支持。在两者相交的情况下,两者都是可以满足的还是只能选其一?

可以有私有扩展方法吗?

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

这个问题问错了。当我们设计这个特性时,语言设计团队问的问题是:

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

由于扩展方法是为了使LINQ工作而设计的,并且LINQ没有扩展方法对某个类型是私有的情况,所以答案是"不,没有这样令人信服的理由"。

通过消除将扩展方法放入静态嵌套类型的能力,在静态嵌套类型中搜索扩展方法的任何规则都不需要被考虑、争论、设计、指定、实现、测试、记录、交付给客户,或者与c# 的每个未来特性兼容。这是一个显著的成本节约。

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

实现扩展文章

中涵盖的最小internal限制

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

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

下面的代码可以编译并运行:

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行,这是编译器说需要的。

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

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

可能有一些非常基本的我们错过了,只是没有使它工作,这就是为什么限制到位。也可能只是他们想要强制编码实践。不幸的是,它不能工作,必须在顶级静态类中。

虽然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它。

对于任何来这里寻找实际答案的人来说,它是

你可以有私人的扩展方法,这里是一个例子,我正在使用的一个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功能,但我不希望它暴露在静态类之外的任何地方。我可以很好地配置WeebleWobbleIMovableProps类中工作,但这只是为了简单地证明这是可能的!

编辑:如果你有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);
        }
    }
}

这取自microsoft msdn上的一个示例。扩展方法必须在静态类中定义。了解如何在不同的名称空间中定义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;
        }
    }
}