让我们说我需要一个简单的私有助手方法,并且直观地在代码中它作为扩展方法是有意义的。有没有办法将该帮助器封装到实际需要使用它的唯一类中?
例如,我试试这个:
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;
}
}
}
仍然没有骰子。现在错误说明:
扩展方法必须在顶级静态类中定义;扩展是一个嵌套类。
这个要求有令人信服的理由吗?在某些情况下,辅助方法确实应该是私有封装的,并且如果辅助方法是扩展方法,则存在代码更清晰且更易读/可支持的情况。对于这两者相交的情况,既可以满足,也可以选择其中一种吗?
答案 0 :(得分:41)
这个要求是否有令人信服的理由?
这是一个错误的问题。我们设计此功能时语言设计团队提出的问题是:
是否有令人信服的理由允许在嵌套静态类型中声明扩展方法?
由于扩展方法旨在使LINQ工作,而LINQ没有扩展方法对某个类型是私有的情况,答案是“不,没有这样令人信服的理由”。
通过消除将扩展方法放在静态嵌套类型中的能力,在静态嵌套类型中搜索扩展方法的规则都不需要考虑,争论,设计,指定,实现,测试,记录,发送到客户,或与C#的所有未来功能兼容。这节省了大量成本。
答案 1 :(得分:19)
我相信在一般情况下你能得到的最好的是internal static
类internal static
扩展方法。因为它将在你自己的程序集中,所以你需要阻止使用扩展的唯一人是程序集的作者 - 所以一些明确命名的命名空间(如My.Extensions.ForFoobarOnly
)可能足以暗示避免滥用。
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;
}
}
}