简体   繁体   English

可以有私有扩展方法吗?

[英]Can There Be Private Extension Methods?

Let's say I have a need for a simple private helper method, and intuitively in the code it would make sense as an extension method.假设我需要一个简单的私有辅助方法,并且在代码中直观地将其作为扩展方法是有意义的。 Is there any way to encapsulate that helper to the only class that actually needs to use it?有什么方法可以将该助手封装到实际需要使用它的唯一类中吗?

For example, I try this:例如,我试试这个:

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

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

The compiler doesn't "see" the GetNext() extension method.编译器不会“看到” GetNext()扩展方法。 The error is:错误是:

Extension method must be defined in a non-generic static class扩展方法必须在非泛型静态类中定义

Fair enough, so I wrap it in its own class, but still encapsulated within the object where it belongs:很公平,所以我将它包装在它自己的类中,但仍然封装在它所属的对象中:

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

Still no dice.仍然没有骰子。 Now the error states:现在错误指出:

Extension method must be defined in a top-level static class;扩展方法必须定义在顶级静态类中; Extensions is a nested class.扩展是一个嵌套类。

Is there a compelling reason for this requirement?这个要求有令人信服的理由吗? There are cases where a helper method really should be privately encapsulated, and there are cases where the code is a lot cleaner and more readable/supportable if a helper method is an extension method.在某些情况下,辅助方法确实应该被私有封装,并且在某些情况下,如果辅助方法是扩展方法,则代码会更清晰且更具可读性/可支持性。 For cases where these two intersect, can both be satisfied or do we have to choose one over the other?对于这两者相交的情况,两者都可以满足还是必须选择一个?

Is there a compelling reason for this requirement?这个要求有令人信服的理由吗?

That's the wrong question to ask.这是一个错误的问题。 The question asked by the language design team when we were designing this feature was:我们在设计这个功能时,语言设计团队提出的问题是:

Is there a compelling reason to allow extension methods to be declared in nested static types?是否有令人信服的理由允许在嵌套静态类型中声明扩展方法?

Since extension methods were designed to make LINQ work, and LINQ does not have scenarios where the extension methods would be private to a type, the answer was "no, there is no such compelling reason".由于扩展方法旨在使 LINQ 工作,并且 LINQ 没有扩展方法对类型私有的场景,因此答案是“不,没有这样令人信服的理由”。

By eliminating the ability to put extension methods in static nested types, none of the rules for searching for extension methods in static nested types needed to be thought of, argued about, designed, specified, implemented, tested, documented, shipped to customers, or made compatible with every future feature of C# .通过消除将扩展方法放在静态嵌套类型中的能力,在静态嵌套类型中搜索扩展方法的任何规则都不需要考虑、争论、设计、指定、实现、测试、记录、交付给客户或与 C# 的每个未来特性兼容 That was a significant cost savings.这是一个显着的成本节约。

I believe the best you can get in general case is internal static class with internal static extension methods.我相信在一般情况下你能得到的最好的是带有internal static扩展方法的internal static类。 Since it will be in your own assembly the only people you need to prevent usage of the extension are authors of the assembly - so some explicitly named namespace (like My.Extensions.ForFoobarOnly ) may be enough to hint to avoid misuse.由于它将在您自己的程序集中,因此您需要阻止使用扩展的唯一人员是程序集的作者 - 因此一些明确命名的命名空间(如My.Extensions.ForFoobarOnly )可能足以提示避免误用。

The minimal internal restriction covered in implement extension article 实现扩展文章中涵盖的最小internal限制

The class must be visible to client code ... method with at least the same visibility as the containing class.该类必须对客户端代码...方法可见,并且至少与包含类具有相同的可见性。

Note: I would make extension public anyway to simplify unit testing, but put in some explicitly named namespace like Xxxx.Yyyy.Internal so other users of the assembly would not expect the methods to be supported/callable.注意:无论如何我都会公开扩展以简化单元测试,但放入一些显式命名的命名空间,如Xxxx.Yyyy.Internal以便程序集的其他用户不会期望支持/可调用这些方法。 Essentially rely on convention other than compile time enforcement.基本上依赖于编译时强制之外的约定。

This code compiles and works:此代码编译并工作:

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

Pay attention to static class Program line which was what the compiler said is needed.注意static class Program行,这是编译器所说的需要的。

I believe it is the way they implemented the compiling of the Extensions Methods.我相信这是他们实现扩展方法编译的方式。

Looking at the IL, it appears that they add some extra attributes to the method.查看 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 
}

There is probably some very fundamental that we are missing that just doesn't make it work which is why the restriction is in place.可能有一些我们遗漏的非常基本的东西并不能让它发挥作用,这就是限制的原因。 Could also just be that they wanted to force coding practices.也可能只是他们想强制编码实践。 Unfortunately, it just doesn't work and has to be in top-level static classes.不幸的是,它不起作用,必须在顶级静态类中。

While the question is corretly answered by Alexei Levenkov I would add a simple example.虽然阿列克谢·列文科夫 ( Alexei Levenkov ) 正确回答了这个问题,我会添加一个简单的例子。 Since the extension methods - if they are private - has no much point, because unreachable outside the containing extension class, you can place them in a custom namespace, and use that namespace only in your own (or any other) namespaces, this makes the extension methods unreachable globally.由于扩展方法 - 如果它们是私有的 - 没有多大意义,因为在包含扩展类之外无法访问,您可以将它们放在自定义名称空间中,并且仅在您自己(或任何其他)名称空间中使用该名称空间,这使得全局无法访问扩展方法。

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 + ")]}";
            }
        }
    }
}

You have to define your namespace twice, and nest your extension namespace inside the other, not where your classes are, there you have to use it by using .你必须定义你的命名空间两次,并将你的扩展命名空间嵌套在另一个命名空间中,而不是你的类所在的位置,你必须通过using来使用它。 Like this the extension methods will not be visible where you are not using it.像这样,扩展方法在您不using地方不可见。

For anyone coming here that may be looking for an actual answer, it is YES .对于来这里寻找实际答案的人来说,答案是肯定的

You can have private extension methods and here is an example that I am using in a Unity project I am working on right now:您可以拥有私有扩展方法,这是我在我现在正在处理的 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;
        }
    }
}

I obviously trimmed down quite a bit, but the gist is that this compiles and runs as expected AND it makes sense because I want the extension method to have Access to the WeebleWobble functionality but I don't want it exposed anywhere outside of the static class.我显然减少了很多,但要点是它按预期编译和运行,这是有道理的,因为我希望扩展方法可以访问WeebleWobble功能,但我不希望它在静态类之外的任何地方公开. I COULD very well configure the WeebleWobble to work in the IMovableProps class but this is just to simply demonstrate that this is possible!我可以很好地将WeebleWobble配置为在IMovableProps类中工作,但这只是为了简单地证明这是可能的!

EDIT: If you have Unity and want to test this here is a MonoBehaviour to test it with (This will not actually apply gravity obviously, but it should change the rotation when you check the inverseGravity box in the inspector during play mode)编辑:如果你有 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);
        }
    }
}

This is taken from an example on microsoft msdn.这是取自 microsoft msdn 上的一个示例。 Extesnion Methods must be defined in a static class.扩展方法必须在静态类中定义。 See how the Static class was defined in a different namespace and imported in. You can see example here http://msdn.microsoft.com/en-us/library/bb311042(v=vs.90).aspx查看静态类如何在不同的命名空间中定义并导入。您可以在此处查看示例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;
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM