可以有私有扩展方法吗?
本文关键字:方法 扩展 | 更新日期: 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 static
类internal 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
功能,但我不希望它暴露在静态类之外的任何地方。我可以很好地配置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);
}
}
}
这取自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;
}
}
}