65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的生成性代码片段

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.38/5 (13投票s)

2008年11月16日

CPOL

7分钟阅读

viewsIcon

46286

downloadIcon

603

使用 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 键在不同的占位符之间移动。通常看起来是这样的:

GenSnippets2.jpg

生成代码片段

正如你可能会同意的,上述机制并不强大。我的意思是,它对于你可以插入的微小事物很有用,但问题在于你不能在代码片段中执行 C#——事实上,Visual Studio 确实提供了 3 个你可以执行的函数,但这些函数对我们来说作用不大。

我希望通过代码片段实现的是让它们变得**参数化**。例如,我想输入 entity3,然后得到一个包含 3 个自动属性的类。经过长时间的思考,我决定要实现这一目标,唯一的方法就是穷举生成。例如,对于实体类,我可能需要一个包含 2 到 20 个成员的类。所以,在一个代码片段文件中,我单独生成**所有情况**。这听起来可能很费力,事实也确实如此,所以,在我们展示示例之前,我想介绍一些关于如何生成代码片段的 C# 代码。如果你打算自己生成代码片段,这才有意义:如果不是,请随意跳到“示例”部分。

它是如何实现的?

生成代码片段的 API 非常简单。事实上,下面的图表大致概括了它:

GenSnippets1.jpg

代码片段在 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(&hellip;, " + 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] 标记的枚举。枚举名称、成员名称和注释是可编辑的。枚举类型取决于你想要的元素数量。还生成 NoneAll 成员,这些成员有时很有用。

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 项目页面上发表评论。

© . All rights reserved.