Click here to Skip to main content
Click here to Skip to main content
Go to top

Generative Snippets in C#

, 15 Nov 2008
Rate this:
Please Sign up or sign in to vote.
Using C# to generate parameterized VS code snippets

Introduction

One of the measures of my efficiency as a developer is how quickly I can spit out well-written, verified code. This challenge is typically met with many mechanisms: code generation programs, code snippets and even the rudimentary cut & paste. In this article, I want to talk about Visual Studio code snippets and the generative snippet mechanism which I use in order to squeeze the maximum value out of snippets. I will also present a showcase of some of the generative snippets I use in everyday work.

Basic Introduction to Snippets

A code snippet is just a chunk of code that you can enter quickly because typing it by hand for the Nth time can be rather boring. Here is an example of one such entry:

#region INotifyPropertyChanged Members
/// <span class="code-SummaryComment"><summary></span>
/// Notifies the caller when a property is changed.
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="propertyName">Name of the property.</param></span>
protected void NotifyPropertyChanged(string propertyName)
{
  if (PropertyChanged != null)
  {
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  }
}
/// <span class="code-SummaryComment"><summary></span>
/// Occurs when a property is changed.
/// <span class="code-SummaryComment"></summary></span>
public event PropertyChangedEventHandler PropertyChanged;
#endregion

To get the above code to appear in VS, I just type a magic combination of letters (npc, in this case), press Tab, and the above block of code is injected at the insertion point.

Some snippets allow you to customize them, i.e., edit parts of them after the snippet has been injected. To allow for this, VS will show placeholders for variables that can be edited. The user can then use the Tab character to move from one placeholder to another. Here’s how it typically looks:

GenSnippets2.jpg

Generative Snippets

The above mechanism, as I’m sure you’ll agree, is not very powerful. I mean, it’s useful for tiny little things that you can inject, but the problem is that you cannot execute C# inside the snippet – in fact, Visual Studio does provide 3 functions that you can execute, but these functions are of little use to us.

What I wanted to do with snippets is make them parameterized. For example, I want to type in entity3 and get a class with 3 auto-properties. After thinking about it long enough, I decided that the only way to get this to work is to generate cases exhaustively. For example, for the entity class I might need one with between 2 and 20 members. So, in a single snippet file, I generate all cases individually. This may sound like hard work, and it is, so before we go to the showcase, I’d like to present some C# code on how snippets are generated. This is only useful if you plan on generating snippets of your own: if not, feel free to skip to the Examples section.

How Is It Done?

The API for generating snippets is really simple. In fact, the following diagram pretty much sums it up:

GenSnippets1.jpg

Code snippets are defined in XML, and the above classes are, basically, objects that help generating this XML a little easier. On the top level, we have the SnippetCollection that appears once per file. It aggregates a number of Snippet object, which define all possible iterations of our generated snippets (e.g., entity1 ... entity10). In addition to the snippets themselves, the user-editable parameters are defined in SnippetLiteral objects that make part of a SnippetLiteralCollection.

Here's a short guide on how to write your own snippets. First, we define the snippet collection:

var sc = new SnippetCollection();

Then, we put as many iterative loops as we require for our snippets. I'll just use one, with a counter from 1 to 10. Inside the loop, we create the Snippet object and set its properties (most of them are mandatory, so I don't recommend skipping any.

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

Literals can be added by explicitly instantiating the SnippetLiteral objects, but there also helper methods in the associated collection class. Let's add a literal to our snippet:

s.Literals.AddLiteral("x", "Variable name");

Now that we've added our literal, we can use it by typing $x$ in the body of the snippet. To create the body (which turns into a CDATA block in the snippet itself), we get the StringBuilder from the snippet and use it. Here's how it's done:

var sb = s.CodeBuilder;
for (int j = 1; j <= i; ++j)
{
  sb.Append("$x$");
  if (j != i)
    sb.Append("*");
}

Now, before exiting the loop, we add the snippet to the snippet collection.

sc.Add(s);

Finally, once we are done with all the loops, we save the snippet collection itself.

sc.Save("pow");

You might need to tweak the Save method to save to the location of your choice, but apart from that, the API presented here can be used without modification, and will output syntactically correct snippet files.

Showcase

Presented below are examples of some of the generative snippets included with the source code. Please note that some examples produce far too much code to be shown here, so I'll provide a textual description instead.

arglistX

Creates a list of comma-separated variables with a common name followed by a 1-based index:

arglist4
T1, T2, T3, T4

arrayX

Creates a declaration of an array with X elements. All elements are initialized to the same value

array5
double[] d = { 1.0, 1.0, 1.0, 1.0, 1.0 };

arrayXbyY

Creates a declaration of a 2D array with X×Y elements. All elements are initialized to the same value, but for square arrays, the diagonal can be initialized separately. Note also that code for these snippets will not be reformatted correctly by 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

Creates X inset for loops with the outer indexer starting at the letter 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

Adds a code stub to run X pieces of code in parallel. Uses AutoResetEvent. Note that in Parallel Extensions, we have Parallel.Invoke() for this.

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

Adds code to catch X different types of exception. Probably the most boring snippet of them all.

catch3
catch (Exception e)
{
}
catch (Exception e)
{
}
catch (Exception e)
{
}

flagsX

Creates a [Flags]-tagged enumeration with X members. Enum name, member names and comments are editable. Enum type depends on how many elements you want to have. Also generates None and All members, which are sometimes useful.

flags3
[System.Flags]
/// <span class="code-SummaryComment"><summary></span>
/// EnumName
/// <span class="code-SummaryComment"></summary></span>
enum EnumName : byte
{
  /// <span class="code-SummaryComment"><summary></span>
  /// None (0)
  /// <span class="code-SummaryComment"></summary></span>
  None = 0,
  /// <span class="code-SummaryComment"><summary></span>
  /// Element1 (1)
  /// <span class="code-SummaryComment"></summary></span>
  Element1 = 1,
  /// <span class="code-SummaryComment"><summary></span>
  /// Element2 (2)
  /// <span class="code-SummaryComment"></summary></span>
  Element2 = 2,
  /// <span class="code-SummaryComment"><summary></span>
  /// Element3 (4)
  /// <span class="code-SummaryComment"></summary></span>
  Element3 = 4,
  /// <span class="code-SummaryComment"><summary></span>
  /// All (7)
  /// <span class="code-SummaryComment"></summary></span>
  All = 7
}

getflagsX

Tests for X flags in an enum, and creates X boolean variables.

getflags4
bool isPrivate = ((modifiers & Private) == Private);
bool isProtected = ((modifiers & Protected) == Protected);
bool isInternal = ((modifiers & Internal) == Internal);
bool isPublic = ((modifiers & Public) == Public);

nulltestX

Test a chain of X properties for null. Chain members are, of course, editable. This snippet is best illustrated in code.

nulltest4
if (a != null && 
  a.Props != null && 
  a.Props.Members != null &&
  a.Props.Members.X != null)
{

}

powX

Inlines a power calculation instead of using Math.Pow(). Takes a specified term to the power of X. This is the example I showed in the previous section.

pow4
t*t*t*t

polyX & polyPX

These two snippet sets both manufacture member functions that compute a polynomial with highest degree X. The difference is that polyX does the calculation using inline multiplication (similar to how powX outputs it) whereas polyPX uses Math.Pow(). The difference in execution speeds is quite dramatic!

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

Declares X variables (variable names starting with ‘a’) in a single line of code.

varlist5
double a = 0, b = 0, c = 0, d = 0, e = 0;

fsmX

Creates a finite state machine with X states. This includes declaration of the fsm enum, creation of Before- and After- EventArgs classes, and the creation of the state machine itself; many elements are optional and can be safely removed. The state machine is quite verbose, so I won't present an example here. To try this snippet, just download the code.

subX & supX

Toy snippets that create subscript and superscript characters. They work in the Consolas font. The main purpose is being able to avoid opening the Character Map while trying to embellish your comments with clever little sub/superscript symbols. A demo cannot be presented here – try it out in Visual Studio.

Entity snippets

Entity snippets create ready-made entity classes. There are several types, with different levels of infrastructure support. Here is a short list of the ones we have so far:

  • tupleXsimple creates a tuple class with X elements.
  • entityXauto creates a class with X auto-properties.
  • arrstoreX generates an array-based storage class with X elements.
  • dpentityXbyY creates a DependencyProperty-based entity class with X read-write properties and Y read-only properties.
  • entityXslim creates a class with X properties whose read and write behavior is regulated with ReaderWriterLockSlim. Note: requires .NET 3.5.

Conclusion

Generating snippets is one of the many ways in which one can easily create parameterized code generation. Though the results achieved are fairly simple, there are situations where this amount of flexibility is sufficient to get the job done. So if this article got you interested, check out the snippets (and the source code) and let me know what you think. You can leave comments here or on the CodePlex project page.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Dmitri Nеstеruk
Founder ActiveMesa
United Kingdom United Kingdom
I work primarily with the .NET technology stack, and specialize in accelerated code production via code generation (static or dynamic), aspect-oriented programming, MDA, domain-specific languages and anything else that gets products out the door faster. My languages of choice are C# and F#, though I'm open to suggestions.
 
I'm a Microsoft MVP (Visual C#) since 2009. I run a collective tech blog at DevTalk.net. I use my own editor called TypograFix to typeset articles and blog posts.
 
Like the article and want this implemented in your product? Got a project that can benefit from Microsoft.Net goodness? Then get in touch!
Follow on   Twitter

Comments and Discussions

 
General5 STARS - EXCELLENT IDEA [modified] PinmemberJohn Adams30-Jun-10 17:14 
GeneralRe: 5 STARS - EXCELLENT IDEA PinmemberDmitri Nesteruk30-Jun-10 21:19 
GeneralMy vote of 2 PinmemberMohammad Dayyan23-Jan-09 17:11 
GeneralGreat! Pinmemberaxelriet20-Dec-08 3:59 
GeneralRe: Great! PinmemberDmitri Nesteruk20-Dec-08 9:40 
GeneralRe: Great! Pinmemberaxelriet20-Dec-08 23:28 
GeneralMy vote of 1 Pinmembermarcusdelance1-Dec-08 8:34 
GeneralCode Generation PinmemberPSUA16-Nov-08 22:23 
GeneralUmmmm PinmvpJohn Simmons / outlaw programmer16-Nov-08 3:46 
GeneralRe: Ummmm PinmemberDmitri Nesteruk16-Nov-08 10:14 
GeneralRe: Ummmm PinmvpNishant Sivakumar16-Nov-08 13:37 
GeneralRe: Ummmm PinmemberMark Nischalke16-Nov-08 16:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 16 Nov 2008
Article Copyright 2008 by Dmitri Nеstеruk
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid