C# 中的生成性代码片段






3.38/5 (13投票s)
使用 C# 生成参数化的 VS 代码片段
引言
衡量我作为开发者的效率的一个指标是,我能够多快地敲出写得好、经过验证的代码。这个挑战通常通过多种机制来应对:代码生成程序、代码片段,甚至是初步的复制粘贴。在本文中,我想讨论 Visual Studio 的代码片段以及我用来最大化代码片段价值的生成代码片段机制。我还将展示一些我在日常工作中使用的生成代码片段。
代码片段基础介绍
代码片段就是一段代码,你可以快速输入它,因为手动输入第 N 次会很无聊。这是一个输入示例:
#region INotifyPropertyChanged Members /// <summary> /// Notifies the caller when a property is changed. /// </summary> /// <param name="propertyName">Name of the property.</param> protected void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } /// <summary> /// Occurs when a property is changed. /// </summary> public event PropertyChangedEventHandler PropertyChanged; #endregion
要让上面的代码出现在 VS 中,我只需输入一个神奇的字母组合(在此例中是 npc
),然后按 Tab 键,这段代码就会被插入到插入点。
有些代码片段允许你自定义它们,即在代码片段插入后编辑其中的部分。为了实现这一点,VS 会显示可编辑变量的占位符。然后,用户可以使用 Tab 键在不同的占位符之间移动。通常看起来是这样的:

生成代码片段
正如你可能会同意的,上述机制并不强大。我的意思是,它对于你可以插入的微小事物很有用,但问题在于你不能在代码片段中执行 C#——事实上,Visual Studio 确实提供了 3 个你可以执行的函数,但这些函数对我们来说作用不大。
我希望通过代码片段实现的是让它们变得**参数化**。例如,我想输入 entity3
,然后得到一个包含 3 个自动属性的类。经过长时间的思考,我决定要实现这一目标,唯一的方法就是穷举生成。例如,对于实体类,我可能需要一个包含 2 到 20 个成员的类。所以,在一个代码片段文件中,我单独生成**所有情况**。这听起来可能很费力,事实也确实如此,所以,在我们展示示例之前,我想介绍一些关于如何生成代码片段的 C# 代码。如果你打算自己生成代码片段,这才有意义:如果不是,请随意跳到“示例”部分。
它是如何实现的?
生成代码片段的 API 非常简单。事实上,下面的图表大致概括了它:
代码片段在 XML 中定义,而上面的类基本上是帮助更轻松地生成此 XML 的对象。在顶层,我们有 SnippetCollection
,它每个文件出现一次。它聚合了许多 Snippet
对象,这些对象定义了我们生成代码片段的所有可能迭代(例如,entity1
...entity10
)。除了代码片段本身,用户可编辑的参数在 SnippetLiteral
对象中定义,这些对象是 SnippetLiteralCollection
的一部分。
以下是如何编写自己的代码片段的简短指南。首先,我们定义代码片段集合:
var sc = new SnippetCollection();
然后,我们添加任意数量的迭代循环来满足我们的代码片段需求。我只使用一个,计数器从 1 到 10。在循环内部,我们创建 Snippet
对象并设置其属性(大多数属性是必需的,因此我不建议跳过任何一个)。
for (int i = 2; i < count; ++i) { var s = new Snippet { Author = author, Description = "Creates an inline multiplication equivalent to Math.Pow(…, " + i + ").", Shortcut = "pow" + i, Title = "pow" + i };
可以通过显式实例化 SnippetLiteral
对象来添加字面量,但关联的集合类中也有辅助方法。让我们向我们的代码片段添加一个字面量:
s.Literals.AddLiteral("x", "Variable name");
现在我们已经添加了字面量,我们可以通过在代码片段的主体中输入 $x$
来使用它。要创建主体(它本身将成为代码片段中的 CDATA 块),我们从代码片段中获取 StringBuilder
并使用它。方法如下:
var sb = s.CodeBuilder; for (int j = 1; j <= i; ++j) { sb.Append("$x$"); if (j != i) sb.Append("*"); }
现在,在退出循环之前,我们将代码片段添加到代码片段集合中。
sc.Add(s);
最后,一旦我们完成了所有循环,我们就保存代码片段集合本身。
sc.Save("pow");
你可能需要调整 Save
方法以保存到你选择的位置,但除此之外,这里提供的 API 可以无需修改即可使用,并会输出语法正确的代码片段文件。
展示
下面是一些包含在源代码中的生成代码片段的示例。请注意,有些示例生成的代码量太大,无法在此处显示,因此我将提供文字描述。
arglistX
创建一个以 1 为基数的索引跟随通用名称的逗号分隔的变量列表。
arglist4 |
T1, T2, T3, T4 |
arrayX
创建一个包含 X 个元素的数组声明。所有元素都初始化为相同的值。
array5 |
double[] d = { 1.0, 1.0, 1.0, 1.0, 1.0 }; |
arrayXbyY
创建一个包含 X×Y 个元素的二维数组声明。所有元素都初始化为相同的值,但对于方形数组,可以单独初始化对角线。还要注意,Visual Studio **不会**正确地重新格式化这些代码片段的代码。
array4by7 |
float[,] f = { { 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }, { 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }, { 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }, { 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f } }; |
array8by8 |
double[,] i = { { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0 } }; |
forCX
创建 X 个嵌套的 for
循环,外层索引从字母 C 开始。
fora5 |
for (int a = 0; a < 10; ++a) { for (int b = 0; b < 20; ++b) { for (int c = 0; c < 30; ++c) { for (int d = 0; d < 40; ++d) { for (int e = 0; e < 77; ++e) { } } } } } |
parrX
添加一个代码存根来并行运行 X 段代码。使用 AutoResetEvent
。请注意,在 Parallel Extensions 中,我们有 Parallel.Invoke()
用于此目的。
parr3 |
AutoResetEvent are1 = new AutoResetEvent(false); AutoResetEvent are2 = new AutoResetEvent(false); AutoResetEvent are3 = new AutoResetEvent(false); ThreadPool.QueueUserWorkItem(delegate { // Thread 1 code here are1.Set(); }); ThreadPool.QueueUserWorkItem(delegate { // Thread 2 code here are2.Set(); }); ThreadPool.QueueUserWorkItem(delegate { // Thread 3 code here are3.Set(); }); WaitHandle.WaitAll(new WaitHandle[] { are1, are2, are3 }); |
catchX
添加代码来捕获 X 种不同类型的异常。可能是所有代码片段中最无聊的一个。
catch3 |
catch (Exception e) { } catch (Exception e) { } catch (Exception e) { } |
flagsX
创建一个带有 X 个成员的 [Flags]
标记的枚举。枚举名称、成员名称和注释是可编辑的。枚举类型取决于你想要的元素数量。还生成 None
和 All
成员,这些成员有时很有用。
flags3 |
[System.Flags] /// <summary> /// EnumName /// </summary> enum EnumName : byte { /// <summary> /// None (0) /// </summary> None = 0, /// <summary> /// Element1 (1) /// </summary> Element1 = 1, /// <summary> /// Element2 (2) /// </summary> Element2 = 2, /// <summary> /// Element3 (4) /// </summary> Element3 = 4, /// <summary> /// All (7) /// </summary> All = 7 } |
getflagsX
测试枚举中的 X 个标志,并创建 X 个布尔变量。
getflags4 |
bool isPrivate = ((modifiers & Private) == Private); bool isProtected = ((modifiers & Protected) == Protected); bool isInternal = ((modifiers & Internal) == Internal); bool isPublic = ((modifiers & Public) == Public); |
nulltestX
测试 X 个属性链是否为 null
。链的成员当然是可编辑的。这个代码片段最好用代码来说明。
nulltest4 |
if (a != null &&
a.Props != null &&
a.Props.Members != null &&
a.Props.Members.X != null)
{
}
|
powX
内联幂运算而不是使用 Math.Pow()
。将指定项计算为 X 次方。这是我在上一节展示的示例。
pow4 |
t*t*t*t |
polyX & polyPX
这两组代码片段都用于生成计算最高次数为 X 的多项式的成员函数。不同之处在于 polyX
使用内联乘法进行计算(类似于 powX
的输出方式),而 polyPX
使用 Math.Pow()
。执行速度的差异非常显著!
poly4 |
public double Poly(double x, double a, double b, double c, double d, double e) { return a * x * x * x * x + b * x * x * x + c * x * x + d * x + e; } |
polyP4 |
public double PolyP(double x, double a, double b, double c, double d, double e) { return a * Math.Pow(x, 4) + b * Math.Pow(x, 3) + c * Math.Pow(x, 2) + d * x + e; } |
varlistX
在一行代码中声明 X 个变量(变量名以“a”开头)。
varlist5 |
double a = 0, b = 0, c = 0, d = 0, e = 0; |
fsmX
创建一个具有 X 个状态的有限状态机。这包括 fsm 枚举的声明、EventArgs
类(Before 和 After)的创建以及状态机本身的创建;许多元素是可选的,可以安全地删除。状态机相当冗长,所以我不会在此展示示例。要尝试这个代码片段,只需下载代码。
subX & supX
用于创建下标和上标字符的玩具代码片段。它们在 Consolas 字体中工作。主要目的是在不打开字符映射表的情况下,用巧妙的下标/上标符号来装饰注释。无法在此演示——在 Visual Studio 中试用一下。
实体代码片段
实体代码片段用于创建现成的实体类。有几种类型,具有不同级别的基础设施支持。以下是我们目前拥有的一些类型的简短列表:
- tupleXsimple 创建一个包含 X 个元素的元组类。
- entityXauto 创建一个包含 X 个自动属性的类。
- arrstoreX 生成一个基于数组的、包含 X 个元素的存储类。
- dpentityXbyY 创建一个基于
DependencyProperty
的实体类,其中包含 X 个读写属性和 Y 个只读属性。 - entityXslim 创建一个包含 X 个属性的类,这些属性的读写行为由
ReaderWriterLockSlim
控制。注意:需要 .NET 3.5。
结论
生成代码片段是创建参数化代码生成的一种方式。尽管取得的结果相当简单,但在某些情况下,这种灵活性足以完成工作。所以,如果本文引起了你的兴趣,请查看代码片段(和源代码),告诉我你的想法。你可以在这里或 CodePlex 项目页面上发表评论。