This article continues my occasional meanderings into dissecting and examining what is essentially a single line of code, while at the same time providing a wealth of context to illustrate how and why to apply the techniques from that line of code in a real application[1].
In this case, while working on an application that needed a component for doing file selection by user-specified masks, I came upon a thread at StackOverflow with a very basic implementation. I added my own contribution and spruced it up in the forum thread, but I was not satisfied. So I went further and packaged it into a user control to perform the file masking - a component to drag into Visual Studio and hook up with just a couple lines of code.
I had a unique requirement, though: besides allowing the user to enter one or more masks (e.g. "User*Items", "*.sql", etc.) I also wanted to only select files satisfying additional, arbitrary constraints (in my case, that each file exist in two separate directories).  To allow this control to be completely generic and have additional customizable constraints, I introduced a callback mechanism, which is commonly done by passing an object that implements a particular interface. With LINQ, though, one can do the same thing with just a bit more panache and with a simpler, cleaner result than the more traditional approach.
I’ll start with a brief digression into some key LINQ concepts, then present and discuss my one line of code, which uses a doubly-nested LINQ construct plus the lambda expression callback mechanism. This makes for quite a springboard for discussing how a completely generic implementation becomes highly customized by how you choose to use it. The final third or so of the article will take you from theory to practice, illustrating how to hook up this user control, and provides two demo applications to guide you.

A Very Brief Introduction to LINQ

Language Integrated Query, or LINQ, is a Microsoft technology introduced in the .NET 3.5 framework. LINQ lets you "…write queries against strongly typed collections of objects." That is, it lets you treat a variety of data - not just databases - as first class data sources. LINQ comes in several flavors to allow you to talk to diverse data sources, including databases, XML streams, and even objects in your program (lists, arrays, dictionaries, etc.). LINQ provides several powerful advantages over more traditional approaches: more concise code; filter, group, and ordering capabilities with little coding; portability due to a common query language for all data sources; and strong typing.
A LINQ query operation consists of just three steps: obtain a data source, create a query, and execute the query. This canonical example from Microsoft's Introduction to LINQ Queries page clearly illustrates the three steps:
class IntroToLINQ
  {

    
static void Main()
    
{
      
// 1. Data source.
      
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

      
// 2. Query creation.
      // evenNumQuery is an IEnumerable<int>
      
var evenNumQuery =
          
from num in numbers
          where
(num % 2) == 0
          select num
;

      
// 3. Query execution.
      
foreach (int num in evenNumQuery)
      
{
          Console.Write
("{0,1} ", num);
      
}
    }
  }
In this example, the data source is simply an array of integers, and the query - with its use of keywords like from, where, and select - resembles a SQL query, though the ordering of clauses is different. It is significant to note that defining the query is separate from its execution. Not until the foreach loop does the data source return any results (which in this case is just a list of the even numbers from the array).

Examples without Overhead

If you like to try the examples as you encounter them, here is a tip to save you from the overhead of creating a Visual Studio project and all it entails:
  • Download LINQPad, a LINQ and C# sandbox/IDE to evaluate expressions, statements, or entire programs. It includes powerful output visualization, particularly useful with LINQ.
  • Instead of using Console.Write calls, use the LINQPad Dump method.
  • To run an entire program - as with the code in Listing 1 - simply strip off the class brackets and paste everything else into LINQPad. Here is the earlier example trimmed down for LINQPad. Set the Language selector in LINQPad to C# Program (rather than the default C# Expression) and you are immediately ready to execute:
void Main()
  
{
    
// 1. Data source.
    
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

    
// 2. Query creation.
    // evenNumQuery is an IEnumerable<int>
    
var evenNumQuery =
        
from num in numbers
        where
(num % 2) == 0
        select num
;

    
// 3. Query execution.
    
evenNumQuery.Dump();
  
}

Two Faces of LINQ

The query in the above example is written in a notation called query syntax (I suppose because it’s reminiscent of a true SQL query), but there is a second notational form called lambda syntax. The two forms are exactly equivalent (where they overlap), and performance is also exactly the same because, during compilation, query syntax expressions are converted to lambda syntax internally. However, the lambda syntax is richer, particularly in C#.
See Query Expression Syntax for Standard Query Operators for a Microsoft reference chart that shows the subset of methods that may be invoked in query syntax in both C# and Visual Basic. Think of that as the theoretical reference, while Brad Vincent turns that information into the applied reference with his quick reference chart, showing side-by-side examples of query syntax and lambda syntax for comparable operations. Another useful Microsoft reference page is Query Syntax vs. Method Syntax (method syntax is another name for lambda syntax).
Here is the LINQ query from the previous example shown side-by-side in query syntax and in lambda syntax, matching up elements from one to the other:
Query Syntax
Lambda Syntax
var evenNumQuery =
    from num in numbers
    where (num % 2) == 0
    select num;
var evenNumQuery =
    numbers
    .Where(num => (num % 2) == 0)
    .Select(num => num);
Table 1. A comparison of code in equivalent Query and Lambda Syntax
However, if an identity transformation is all that is needed in the Select() method, it may be safely omitted, shortening the lambda syntax in this example to just this:
 var evenNumQuery = numbers.Where(num => (num % 2) == 0);
When should you use query syntax vs. lambda syntax? It is largely a matter of preference. Lambda syntax is often more concise and so makes for more readable code, but multiple joins are much cleaner with query syntax. Lambda syntax also provides a superset over query syntax, so some tasks require lambda syntax. If you’re feeling adventurous, you can even mix the two syntaxes, but this should be done judiciously (e.g. adding a Single() to the end of an expression using query syntax). Further discussion of the pros and cons is available here.

Lambda Expressions

The second form of LINQ syntax is called lambda syntax because of its use of lambda expressions. Though it sounds intimidating the first time you hear about it, a lambda expression is simply a function written in a peculiar syntax to facilitate writing it as an expression. This allows it to be placed into a larger expression, making it well-suited for functional programming. In the example above, the first lambda expression is:
num => (num % 2) == 0
The equals-greater than token is supposed to visually represent an arrow, and may be read as “goes to”, “maps to”, “becomes” or “such that”; basically, this arrow separates inputs from outputs. Here we have a single input, num, and a single output, the Boolean expression (num % 2) == 0. Lambda expressions may have one or more inputs, but they always have a single output. This lambda expression states that you want a num such that num is even, since only even numbers modulo 2 are equal to zero. You’re using a lambda expression when calling a method that takes a delegate as a parameter, and the Where() and Select() methods used above are two such methods. (They are just a couple of examples of the many LINQ extension methods that take delegates.)

Distilling Delegates Down to Lambda Expressions

The example used so far shows how to supply a lambda expression, so this section will discuss how to consume it. The code sample below shows the use of delegates distilled to its simplest case. First you define a signature, in this case a method that takes an integer argument and returns an integer result, performing some mapping on it. Next, you create a method that conforms to that signature, in this case one that squares the integer value (SquareIt). Then set up an instance of the delegate to point to the SquareIt method so that when you call the delegate you are actually calling the referenced method; passing an input value of 5, for example, yields an output value of 25.
public delegate int MapAnInt(int x); // define a delegate signature
static public int SquareIt(int x) // define a method fitting the signature{  return x * x;}
void
Main(){
    MapAnInt myDelegate
= new MapAnInt(SquareIt); // connect them
    
myDelegate(5).Dump();}
With the advent of C# 2.0 you could condense the code by using an anonymous delegate right in place:
public delegate int MapAnInt(int x);

void Main(){
    MapAnInt myDelegate
=
        new
MapAnInt( delegate(int x) { return x * x; } );
    
myDelegate(5).Dump();}
And with C# 3.0 you could replace the anonymous delegate with a lambda expression - recall that a lambda expression may be used wherever a delegate may be used - yielding a much more concise and arguably clearer bit of code:
public delegate int MapAnInt(int x);
void Main(){
    MapAnInt myDelegate
= x => x * x;
    
myDelegate(5).Dump();}
(Thanks to Eric White for his blog entry on lambda expressions, from which I adapted the above syntax progression.)
The code has shrunk considerably, but you can do better still! In this latest rendition, there are two separate chunks of code: the delegate declaration and the delegate usage. A key reason for using anonymous delegates in the first place is to have it all right there, without needing to create any other pieces and then find useful places to store them. The .NET framework thoughtfully provides a set of built-in generic delegates to help you do just that. There is a set of generic delegates for up to four arguments, both for functions that return a value (Func<…>) and functions that do not (Action<…>). Here is the previous, concise code snippet made even more concise by using the built-in Func delegate that takes one argument and returns a value:
void Main(){
    Func
<int, int> myDelegate = x => x * x;
    
myDelegate(5).Dump();}
In this example, the input is an int and so is the return value, so which is which? By convention, the last element in the list is always the type of the return value. If the delegate returned a string instead, the type declaration above would have been Func<int, string> myDelegate… . Here is a list of the built-in function delegate declarations:
public delegate TResult Func<TResult>();
public delegate TResult Func<T1, TResult>(T1 arg1);
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3);
public delegate TResult Func<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
The Action delegates are analogous but without return values, and as long as you have no more than four arguments, you can use those to save yourself from the explicit creation of a delegate. See The Built-In Generic Delegate Declarations for more details, and for reference, the Func<T1, Result> delegate (just to pick the one used in the example) is documented here on MSDN.
If you are creating an application or libraries for your own use, I would always recommend using the built-in delegates, but I have found a circumstance where it was better not to use them - in my open-source libraries. These libraries are for general consumption, and to make them as broadly applicable as possible, I decided when .NET 3.5 was very new to compile the libraries to .NET 2.0 using the LINQBridge library[2]. I still think that there is enough corporate inertia behind .NET 2.0, so I continue to support it.
If I had used the built-in delegates in my libraries, then when you came along and wanted to use them, Visual Studio would have required you to add a reference to the LINQBridge.dll in your project, whether you compiled under .NET 2.0 or 3.5. I consider that unacceptable, as it should be transparent to you, the application developer. Defining my own delegates was the way around this; your application will still pull in LINQBridge.dll when it compiles (along with several other DLLs that are dependencies in my libraries) but it will be totally transparent to you.
For further study, you may wish to peruse Anonymous Methods or Lambda Expressions in Microsoft’s C# Programming Guide.

An Interesting Line of Code

Now that you can understand lambda expressions and how to use delegates, the one line of code for file masking should be straightforward (Listing 1), and can execute immediately in LINQPad if you want to try it. The declarations at the top include the familiar delegate declaration and definition, and the Main method contains the one line of code to be discussed. The sample concludes with a support method (FitsMask) that returns a Boolean value indicating whether the supplied file name qualifies for inclusion based on the supplied file mask.

delegate bool RestrictionLambdaDelegate(string fileName);

// Simulated parameters for the code below
RestrictionLambdaDelegate RestrictionLambda =
  
f => DateTime.Compare(File.GetLastWriteTime(f), EarliestDate) >= 0;string SourceDirectory = @"C:\temp\demo";string Mask = "*.xls|*.doc";DateTime EarliestDate = DateTime.Parse("2009-05-01T19:35:00.0000000Z");// End of parameters
void Main(){
   
// The one line of code
   
(from f in Directory.GetFiles(SourceDirectory)
   
where Mask
        
// convert compound masks to enumerable list to allow Any() call
        
.Split(new string[] { "\r\n", "\n", ",", "|", " " },
        
StringSplitOptions.RemoveEmptyEntries)
        
// Any extension method uses lambda expression to match file to any mask
        
.Any(fileMask => FitsMask(Path.GetFileName(f), fileMask))
        
// custom lambda expression allows further arbitrary restriction
        
&& (RestrictionLambda == null || RestrictionLambda(f))
   
orderby Path.GetFileName(f)
   
select f
  
).Dump(); // LINQPad display method}
private bool FitsMask(string fileName, string fileMask)
  
{
    
try
    
{
        
string pattern =
          
'^' + // anchor start of pattern
          
Regex
      
    // store off key metacharacters then escape any other ones
          
.Escape(fileMask
            .Replace
(".", "__DOT__")
            
.Replace("*", "__STAR__")
            
.Replace("?", "__QM__"))
          
// then restore the saved metacharacters as regex chars
          
.Replace("__DOT__", "[.]")
          
.Replace("__STAR__", ".*")
          
.Replace("__QM__", ".")
          + '$';
// anchor end of pattern
          
return new Regex(pattern, RegexOptions.IgnoreCase).IsMatch(fileName);
    
}
     
catch (ArgumentException)
    
{ return false; } // just return no match
  
}
Listing 1. File Mask Demo Program
Figure 1 shows zooms in on the key portion of code, highlighting the doubly-nested LINQ queries for clarity. There are a number of points to notice here:
  • The outer LINQ query uses query syntax while the inner query uses lambda syntax. Mixing them with these simple constructs does not add confusion or complexity. The inner query must use lambda syntax, since the Any extension method does not have an equivalent in query syntax form. The outer query could be written either way and is about as complex in either form.
  • The inner query is the single Any extension method call; the Split in front of it just converts the input (a string stored in Mask) into an IEnumerable<string> collection, allowing you to send in multiple masks separated by any combination of the separator strings given.
  • The Any method takes a delegate argument; here it is receiving a lambda expression that answers the question: does there exist any file mask such that the base file name satisfies the mask?
  • The outer query consists of a simple from…where…orderby…select sequence.
  • The outer query returns a list (sorted by file name) of all files in the specified source directory where the file name matches at least one of the specified file masks and where the file satisfies the arbitrary, user-supplied RestrictionLambda.
  • The user-supplied RestrictionLambda is optional. If it is never defined, then the gatekeeper expression (the check for null) avoids attempting to execute it, thus avoiding a cataclysm of, well, minor proportions.
Figure 1. One Line of Code
This single line of functional programming code returns a list of files from a specified directory matching one or more file masks and satisfying an additional, unspecified constraint, supplied by the calling application at runtime.

C# Injection or Dynamic C#?

You may be familiar with SQL injection, a technique that exploits an oft-found vulnerability, allowing an attacker to sneak in arbitrary SQL statements through application code to examine, or even corrupt, a system. Needless to say, SQL injection is a very bad thing. On the other hand, consider dynamic SQL (also referred to as embedded SQL), a technique where one embeds SQL statements into application code to achieve specific designs. Sounds rather similar to SQL injection, no? If used judiciously, dynamic SQL is a good thing. Used carelessly, it is indeed a well-oiled path to SQL injection.
Lambda expressions are analogous in the C# universe: they let you embed arbitrary C# code into other C# code. Unlike SQL, though, the arbitrary code is supplied at compile time - the user does not have the ability to send a string of code and thus be a potential source of attack (It works with SQL because SQL commands are still just strings at runtime). So, with C# you have the good part of injection without the bad. Typically, you have a library routine written in such a way as to accept code injection through lambda expressions, and you have your main application which invokes that library, passing in its particular code fragments to do what it wants done. Let’s get into the details of that process.

Customization with Lambda Expressions

Though the one line of code in Figure 1 contains other interesting aspects of LINQ as already discussed, the focus of this article is the innocuous call to the RestrictionLambda method highlighted in red. Listing 1 has shown this as top-level application code, but in reality the code is in a library containing a general-purpose file masking control (called, not too surprisingly, the FileMaskControl).Without the RestrictionLambda call, the code produces a list of files matching one or more supplied masks. Since the set of masks is determined by the user, the routine already allows some custom behavior in a sense, but within the narrow confines of file name patterns. Now add the RestrictionLambda call, and suddenly the generic code is highly customizable, limited only by what you can frame within the bounds of a lambda expression (which is hardly a limit at all). Figure 2 illustrates this functionality. Start with the entire contents of some selected directory (frame 1). As you move to the second frame, apply a file mask to filter the original list. The figure shows two masks applied: "Conn*" and "*Data*". File masks allow you to only select by naming patterns. The lambda expression filter (applied in the third frame) lets you select by arbitrary criteria; in this case with the same date filter you have seen previously.
Figure 2. Visualizing The Two-Stage Filtering Process
The shaded files (and more that are scrolled off the panel) in the leftmost frame survive the file mask filter into the middle frame. The shaded files in the middle successfully pass the added restriction imposed by the lambda expression to end up in the rightmost frame.
You can define RestrictionLambda to be any lambda function that satisfies the signature required by the code; recall that the delegate declaration specifies the signature. The delegate in Listing 1, repeated below, specifies that the lambda expression must take a single input (a file name) and return a Boolean output where true indicates the file meets the specified criteria, and false indicates the file fails to meet the criteria:
delegate bool RestrictionLambdaDelegate(string fileName);
In the context of the FileMaskControl, any lambda expression you specify should be relevant to some characteristic of a file. Here are just a few examples of additional restrictions (on top of those imposed by the user-specified file masks that are built-in to the control). Not surprisingly, they make copious use of  File, Path, and FileInfo methods.

Restrict files to those after a certain date

RestrictionLambda =
    f => DateTime.Compare(File.GetLastWriteTime(f), EarliestDate) >= 0;
In common parlance: file f, such that the modification time of f is later than some specified date.

Include only files that are in a separate list of master files

RestrictionLambda =
    (f => File.Exists(Path.Combine(ActivesPath, Path.GetFileName(f))));
Translation: file f, such that a file of the same name also exists in a specified directory.

Ignore files marked as deprecated

RestrictionLambda =
    f => !Path.GetFileName(f).ToLower().Contains("deprecated");
Translation: file f, such that its name does not contain the word "deprecated", ignoring case.

Require files to be unlocked

RestrictionLambda =
    f => (File.GetAttributes(f) & FileAttributes.ReadOnly) == 0;
Translation: file f, where its read-only attribute is not enabled.

Restrict files to those smaller than a certain size

RestrictionLambda =
    f => (new FileInfo(f).Length < MaxFileSize);
Translation: file f, where its length is less than a specified size.

Putting Theory into Practice

You’ve now had a complete sample program to experiment with, plus several lambda expressions to inject into the code. But folding the concepts into a practical context or real-world application should help solidify understanding.
The code in Listing 1 comprises the bulk of the FileMaskControl available from my open source libraries[3]. The API for the control is available here.Visually, the control consists of one input and two output components (Figure 3, right-hand side). The input component is a type-in box for file masks bound to the Mask property. The output components include a list box displaying the names of the matched files bound to the FileList property, and a label displaying the count of matched files.
The left side of Figure 3 shows the quite simple class diagram for the control. Besides the constructor, it has a single public method (UpdateFileMatches) that refreshes the output components on demand. Of the seven public properties, five of them have visual correlations as indicated. The remaining two non-visual properties specify the directory to search and the familiar RestrictionLambda lambda expression.
Figure 3. The FileMask Control
The left side shows the details of the control in a class diagram, relating the appropriate parts to the visual representation of the control on the right. Using this control in your own application is quite simple:
  • Download my open source libraries[3] and unzip.
  • In Visual Studio, add the control from the CleanCode.GeneralComponents.dll library to your toolbox in the visual designer (right-click in the toolbox and select Choose Items…).
  • Drag the newly-added FileMaskControl from the toolbox onto the designer surface.
  • Wire up the control in your code as described next.
Hooking it up involves, at a minimum, setting the Mask and SourceDirectory properties. A typical starting value for the file mask is just “*.*” to see all files, and you should set the source directory to your appropriate location. Optionally, assign a lambda expression to the RestrictionLambda property:
fileMaskControl.Mask = "*.*"; // (or mask(s) of your choice)fileMaskControl.SourceDirectory = "C:\temp"; // or directory of your choicefileMaskControl.RestrictionLambda =
    
f => (new FileInfo(f).Length < MaxFileSize);
      
Once you have initialized properties, tell the control to reflect your inputs with the UpdateFileMatches method when the control becomes visible (or immediately, if it is on your main form):
fileMaskControl.UpdateFileMatches();
Accompanying this article is a Visual Studio solution that provides two simple scenarios to get you started immediately[4]. The first demo (Figure 4) combines a FileMaskControl with a directory selector to let the user set the SourceDirectory property. As it’s designed to show the fewest lines of code needed to wire up the control, this demo does not use the RestrictionLambda filter. Figure 4 shows a typical default starting mask, and because no directory has been selected, the output box on the right shows no matching files yet.
Figure 4. The Basic FileMask Demo - This demo requires just a few lines of code to hookup the directory selector to the file mask control.
The second demo adds in a lambda expression filter, allowing you to dynamically include or exclude at runtime the RestrictionLambda expression that requires files to be later than a user-selectable date (Figure 5). Here a set of multiple masks are present along with a directory, so the output list already shows some results. Additionally, the lambda expression filter is applied by checking the Enabled box and selecting a date. Change the date, or disable it with the checkbox, and the resulting list of files will immediately update - whether you see any difference obviously depends on what is in your directory, though!
Figure 5. The Customized FileMask Demo
This demo lets you interactively include or exclude a lambda expression to restrict files to a certain date range.
You have already seen the particular lambda expression used both in Listing 1 and in the list of sample lambda expressions, but here it is once again in context, showing how it uses the date supplied by the user. The code fragment also takes into account whether the user has enabled or disabled the date picker, and therefore the lambda expression. Setting the delegate to null, as was shown in Listing 1, effectively disables it. Again, that simple assignment is all you need to customize the generic FileMaskControl to your application. Apply the same technique to your own libraries to make customizable, generic components.
if (dateTimePicker.Enabled){
    earliestDate
= dateTimePicker.Value;
    
fileMaskControl.RestrictionLambda =
       
f => DateTime.Compare(File.GetLastWriteTime(f), earliestDate) >= 0;}else{
    fileMaskControl.RestrictionLambda
= null;}
fileMaskControl.UpdateFileMatches
();
For completeness, Listing 2 shows the complete hand-hewn portion of the second demo (i.e. it does not include the parts generated by Visual Studio's designer). There are just three event handlers for the three supporting visual components (highlighted in the listing): the directory picker, the date/time picker, and the enabled/disabled checkbox. Follow the code from there and you will see the simple code needed to interact with the FileMaskControl.
namespace FileMask_Demo_Customized
{
    
public partial class Form1 : Form
    {
      DateTime earliestDate
;
     
      
public Form1()
      
{
          InitializeComponent
();
      
}
          

      private void
directoryPickerButton_Click(object sender, EventArgs e)
      
{
          FolderBrowserDialog folderPicker
= new FolderBrowserDialog();
          
DialogResult result = folderPicker.ShowDialog();
          
if (result == DialogResult.OK)
          
{
              directoryTextBox.Text
= folderPicker.SelectedPath;
              
fileMaskControl.SourceDirectory = folderPicker.SelectedPath;
              
fileMaskControl.UpdateFileMatches();
          
}
      }
     
      
private void Form1_Load(object sender, EventArgs e)
      
{
          fileMaskControl.Mask
= "*.*";
      
}

      
private void enabledCheckBox_CheckedChanged(object sender, EventArgs e)
      
{
          dateTimePicker.Enabled
= enabledCheckBox.checked;
          
SetRestrictionByDate();
      
}

      
private void dateTimePicker_ValueChanged(object sender, EventArgs e)
      
{
          SetRestrictionByDate
();
      
}

      
private void SetRestrictionByDate()
      
{
        
if (dateTimePicker.Enabled)
        
{
             earliestDate
= dateTimePicker.Value;
             
fileMaskControl.RestrictionLambda =
                
f => DateTime.Compare(File.GetLastWriteTime(f), earliestDate) >= 0;
        
}
        
else
        
{
             fileMaskControl.RestrictionLambda
= null;
        
}
        fileMaskControl.UpdateFileMatches
();
      
}
    }
  }
Listing 2. Application Program to Manipulate the FileMaskControl

Conclusion

LINQ is a great leap forward in code abstraction. By  providing a uniform way to handle widely diverse data structures within a program (not to mention diverse external data sources I’ve not even touched upon in this article), LINQ makes code easier to create and, perhaps more importantly, easier to maintain. It also provides tremendous flexibility in what it can do. But more than that, it is even flexible in its own use (is meta-flexible a real word?) offering two, quite different notations: query syntax and lambda syntax.
Query syntax has the advantage of looking like a SQL query, and makes some typical SQL-type operations (e.g. joins) simpler to write.  On the other hand, Lambda syntax allows for richer expressiveness with its larger operator set, as well as access to one of the most powerful aspects of LINQ, lambda expressions. As demonstrated in this article, lambda expressions allow you to inject arbitrary functions into any properly instrumented code. This gives you, as the designer, the capability to design completely generic building blocks with hooks (i.e. callback mechanisms) that can convert your building blocks into extremely specialized components. 
The FileMaskControl I’ve described shows just one example of a practical application of this design pattern, illustrating some useful techniques to help you write cleaner, tighter code. Indeed, the amount of code directly related to the customization hooks for the FileMaskControl is a grand total of two lines: one internal line to use the lambda expression, and one external line to set a lambda expression. With the principles I’ve described you should now have a good foundation for using lambda expressions to develop your own library of building blocks.

Comments ( 0 )