61

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

例如,我试试这个:

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

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

4

7 回答 7

55

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

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

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

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

通过消除将扩展方法放入静态嵌套类型的能力,无需考虑、争论、设计、指定、实现、测试、记录、交付给客户或与 C# 的所有未来功能兼容。这是一个显着的成本节约。

于 2013-05-24T17:13:28.233 回答
29

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

实现扩展文章internal中涵盖的最小限制

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

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

于 2013-05-24T17:16:52.607 回答
2

此代码编译并工作:

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

于 2013-05-24T17:01:01.557 回答
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 
}

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

于 2013-05-24T17:16:48.507 回答
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

于 2020-08-15T03:18:38.867 回答
0

对于任何来到这里可能正在寻找实际答案的人来说,这是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);
        }
    }
}
于 2021-07-22T17:42:57.990 回答
-2

这取自 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;
        }
    }
}
于 2013-05-24T17:23:35.757 回答