Transform collection to comma separated list

Today I had to transform a collection of integers into a string in the form of a comma separated list. In the ‘old-style- approach it would probably be something like:

  1: List<int> numbers = new List<int>() { 1, 2, 3, 4, 5 };
  2: StringBuilder strb = new StringBuilder();
  3: foreach (int number in numbers)
  4: {
  5:     strb.Append(number.ToString());
  6:     strb.Append(",");
  7: }
  8: string commeSeparatedList = strb.ToString().TrimEnd(',');

But actually this can be done in a short way, like:

  1: List<int> numbers = new List<int>() { 1, 2, 3, 4, 5 };
  2: string commeSeparatedList = string.Join(",", numbers.Select(n => n.ToString()).ToArray());

which produces the same result. In my case it was not a collection of integers, but a list of objects; but is the same approach:

  1: string commaSeparatedListOfGroups = string.Join(",", allRelatedGroups.Select(g => g.Id.ToString()).ToArray());
May 7, 2010 05:14 by lustuyck
E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Clone objects

Today a collegue asked for a clean way to clone objects in C#,  so that you really have different objects instead of a reference copy . So imagine that you have a class Employee as follows:

  1: public class Employee
  2: {
  3:      public string FirstName { get; set; }
  4:      public string LastName { get; set; }
  5: }

and you would copy it:

  1: Employee original = new Employee()
  2: {
  3:     FirstName = "Homer",
  4:     LastName = "Simpson",
  5: };
  6: 
  7: Employee copy = original;
  8: copy.FirstName = "Marge";
  9: Console.WriteLine(string.Format("{0} {1}", original.FirstName, original.LastName));
 10: Console.WriteLine(string.Format("{0} {1}", copy.FirstName, copy.LastName));

Changing the first name of the copied object obviously also changes the first name of the original object, because it has been copied by refererence and so original and copy refer to the same object:

CO_01

And this was not wat we wanted.

To solve it, we first write an extension method that will be available on each class that implements the ICloneable interface:

  1: static class Extensions
  2: {
  3:    public static T Clone<T>(this T objectToClone) where T : ICloneable
  4:    {
  5:      return (T)objectToClone.Clone();
  6:    }
  7: }

Next we have to make the Employee class implement the ICloneable interface:

  1: public class Employee : ICloneable
  2: {
  3:   public string FirstName { get; set; }
  4:   public string LastName { get; set; }
  5: 
  6:   #region ICloneable Members
  7: 
  8:   public object Clone()
  9:   {
 10:     return new Employee() {FirstName = FirstName, LastName = LastName};
 11:   }
 12: 
 13:   #endregion
 14: }

And if we now use the Clone method like:

  1: Employee copy = (Employee)original.Clone();

then the new object is really a copy of the original:

CO_02

If you want to clone a list of objects, add an extension method like this:

  1: static class Extensions
  2: {
  3:    public static IList<T> Clone<T>(this IList<T> listToClone) where T : ICloneable
  4:    {
  5:      return listToClone.Select(item => (T)item.Clone()).ToList();
  6:    }
  7: }

and to clone this list is then very simple:

  1: IList<Employee> copiedList = originalList.Clone();

This example clones only the Employee object (known as shallow copy, see here), but what if the Employee class contains other reference types? In this case we have to implement a deep copy, meaning that every subclass has to implement ICloneable too, for example, if you have a City:

  1: public class City: ICloneable
  2: {
  3:   public string CityName { get; set; }
  4: 
  5:   #region ICloneable Members
  6: 
  7:   public object Clone()
  8:   {
  9:     return new City() { CityName = CityName };
 10:   }
 11: 
 12:   #endregion
 13: }

then Employee has to clone it like:

  1: public class Employee : ICloneable
  2: {
  3:   public string FirstName { get; set; }
  4:   public string LastName { get; set; }
  5:   public City City{ get; set; }
  6: 
  7:   #region ICloneable Members
  8: 
  9:   public object Clone()
 10:   {
 11:     return new Employee() {FirstName = FirstName, LastName = LastName, City = (City)City.Clone() };
 12:   }
 13: 
 14:   #endregion
 15: }

We can do one other improvement. As you see we always have to cast the Clone operation to its actual type. To solve  that we can create a generic ICloneable interface:

  1: interface ICloneable<T> : ICloneable
  2: {
  3:   new T Clone();
  4: }

and adapt the classes to it:

  1: public class Employee : ICloneable<Employee>
  2: {
  3:   public string FirstName { get; set; }
  4:   public string LastName { get; set; }
  5:   public City City{ get; set; }
  6: 
  7:   #region ICloneable<Employee> Members
  8: 
  9:   Employee ICloneable<Employee>.Clone()
 10:   {
 11:     return new Employee() { FirstName = FirstName, LastName = LastName, City = City.Clone() };
 12:   }
 13: 
 14:   #endregion
 15: 
 16:   #region ICloneable Members
 17: 
 18:   object ICloneable.Clone()
 19:   {
 20:     return new Employee() { FirstName = FirstName, LastName = LastName, City = City.Clone() };
 21:   }
 22: 
 23:   #endregion
 24: }
 25: 
 26: public class City : ICloneable<City>
 27: {
 28:   public string CityName { get; set; }
 29: 
 30:   #region ICloneable<City> Members
 31: 
 32:   public City Clone()
 33:   {
 34:     return new City() { CityName = CityName };
 35:   }
 36: 
 37:   #endregion
 38: 
 39:   #region ICloneable Members
 40: 
 41:   object ICloneable.Clone()
 42:   {
 43:     return new City() { CityName = CityName };
 44:   }
 45: 
 46:   #endregion
 47: }
May 7, 2010 02:42 by lustuyck
E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Modifying namespace in XML document programmatically

I needed to validate an XML document with a given XSD document. Seems easy enough… so let’s have a look at the schema first:

  1: <?xml version="1.0" encoding="utf-8"?>
  2: <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"           
  3:                  xmlns="http://my.namespace"          
  4:                  elementFormDefault="qualified"           
  5:                  targetNamespace="http://my.namespace"> 
  6:   <xs:element name="customer">   
  7:     <xs:complexType>     
  8:       <xs:sequence>       
  9:       <xs:element name="firstname" type="xs:string" />       
 10:       <xs:element name="lastname" type="xs:string" />       
 11:       <xs:element name="age" type="xs:integer" />     
 12:       </xs:sequence>   
 13:     </xs:complexType> 
 14:   </xs:element>
 15: </xs:schema>

The XML instance is:

  1: <?xml version="1.0" encoding="utf-8" ?>
  2: <customer>
  3:   <firstname>Homer</firstname>
  4:   <lastname></lastname>
  5:   <age>36</age>
  6: </customer> 

The code is straightforward:

  1: static void Main(string[] args)
  2: {
  3:   // Load the xml document
  4:   XDocument source = XDocument.Load(@"instance.xml");
  5:   // Load the schema
  6:   XmlSchemaSet xmlSchemaSet = new XmlSchemaSet();
  7:   xmlSchemaSet.Add(null, XmlReader.Create(@"customer.xsd"));
  8:   // Validate
  9:   try { source.Validate(xmlSchemaSet, ValidationCallback, true); }
 10:   catch (Exception ex) { Console.WriteLine(ex.Message); }
 11: }
 12: static void ValidationCallback(object sender, System.Xml.Schema.ValidationEventArgs e)
 13: {
 14:   Console.WriteLine(string.Format("[{0}] {1}", e.Severity, e.Message));
 15: } 

If you run this, no errors are thrown so it seems to validate. To be sure, let’s change the age in an invalid value:

  1: <Age>invalid!</Age>

and test again. Well… actually, no validation error is thrown in this case either… what’s going on here?

Actually, the XML is not validated at all, because it’s not in the same namespace (http://my.namespace) as the schema definition. This is very dangerous, as we might easily get mislead by thinking that it validates because no errors are thrown. So how do we solve it?

We could ask the sender to provide the correct namespace in the XML file – this would be the best solution because then it would just work – if you try to validate the following XML:

  1: <?xml version="1.0" encoding="utf-8" ?>
  2: <customer xmlns="http://my.namespace">
  3:   <firstname>Homer</firstname>
  4:   <lastname></lastname>
  5:   <age>invalid</age>
  6: </customer>

…then the validation error is thrown, because the namespaces now match:

xmlnamespace_01

Unfortunately, it is not always possible to change the XML file, so how can we bypass this namespace conflict? If appears that if we would change the namespace in the loaded XML document to the one we are using in our schema, the conflict is resolved. A first attempt may be:

  1: // Load the xml document
  2: XDocument source = XDocument.Load(@"instance.xml");
  3: // Change namespace to reflect schema namespace
  4: source.Root.SetAttributeValue("xmlns", "http://my.namespace");
  5: // Load the schema
  6: XmlSchemaSet xmlSchemaSet = new XmlSchemaSet();
  7: xmlSchemaSet.Add(null, XmlReader.Create(@"customer.xsd"));
  8: // Validate
  9: try { source.Validate(xmlSchemaSet, ValidationCallback, true); }
 10: catch (Exception ex) { Console.WriteLine(ex.Message); }

If we run this, the validation error is still not thrown, so setting the namespace attribute is not enough. The reason is that once the XDocument is loaded, every element in the tree gets prefixed with the namespace name. So we need to change them all, and so I wrote the following method that does this:

  1: static void Main(string[] args)
  2: {
  3:   // Load the xml document
  4:   XDocument source = XDocument.Load(@"instance.xml");
  5:   // Change namespace to reflect schema namespace
  6:   source = SetNamespace(source,"http://my.namespace");
  7:   // Load the schema
  8:   XmlSchemaSet xmlSchemaSet = new XmlSchemaSet();
  9:   xmlSchemaSet.Add(null, XmlReader.Create(@"customer.xsd"));
 10:   // Validate
 11:   try { source.Validate(xmlSchemaSet, ValidationCallback, true); }
 12:   catch (Exception ex) { Console.WriteLine(ex.Message); }
 13: }
 14: 
 15: public static XDocument SetNamespace(XDocument source, XNamespace xNamespace)
 16: {
 17:   foreach (XElement xElement in source.Descendants())
 18:   {
 19:     // First make sure that the xmlns-attribute is changed
 20:     xElement.SetAttributeValue("xmlns", xNamespace.NamespaceName);
 21:     // Then also prefix the name of the element with the namespace
 22:     xElement.Name = xNamespace + xElement.Name.LocalName;
 23:   }
 24:   return source;
 25: }
 26: 
 27: static void ValidationCallback(object sender, System.Xml.Schema.ValidationEventArgs e)
 28: {
 29:   Console.WriteLine(string.Format("[{0}] {1}", e.Severity, e.Message));
 30: }

The SetNameSpace method will set the corrrect namespace for each element in the XDocument. And if we run it now, the validation error is thrown again because the namespace in the XDocument has been modified and matches the schema namespace.

May 6, 2010 07:22 by lustuyck
E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

I love lambda expressions (part 2)!

 

In previous part we learned about lamda expressions, and ended up with the following example:

  1: List<int> numbers = new List<int> { 1, 7, 16, 23, 41, 66 };
  2: Func<int, bool> funcNumbersSmallerThan25 = n => n < 25;


The delegate funcNumbersSmallerThan25 is compiled into executable code and can be called as follows:

  1: List<int> numbersSmallerThan25 =
  2:       numbers.Where(funcNumbersSmallerThan25).ToList();

 

Expression trees

Now it’s time to introduce another feature called expression trees. An expression tree is a tree structure that contains lambda expressions. To demonstrate, let’s define the delegate as an expression using the following syntax:

  1: Expression<Func<int, bool>> exprNumbersSmallerThan25 = n => n < 25;


Now the delegate exprNumbersSmallerThan25 is no longer compiled to executable code, but instead compiled as data. In order to use it, you have to compile it to IL first:

  1: List<int> numbers = new List<int> { 1, 7, 16, 23, 41, 66 };
  2: Expression<Func<int, bool>> exprNumbersSmallerThan25 = n => n < 25;
  3: var originalDelegate = exprNumbersSmallerThan25.Compile();
  4: List<int> numbersSmallerThan25 = numbers.Where(originalDelegate).ToList();    

 

If you call the ToString() method of the expression:

  1: string lambda = exprNumbersSmallerThan25.ToString();


…you get back the lamda expression of the delegate:

lambda01

So what we did was create a delegate exprNumbersSmallerThan25  using the lambda expression ‘n => n < 25′, compiled it to IL code and used it. Instead of writing it using the lamba expression, you can also write it without the lamda expression:

  1: List<int> numbers = new List<int> { 1, 7, 16, 23, 41, 66 };
  2: ParameterExpression n = Expression.Parameter(typeof(int), "n");
  3: Expression body = Expression.LessThan(n, Expression.Constant(25));
  4: Expression<Func<int, bool>> exprNumbersSmallerThan25
  5:                        = Expression.Lambda<Func<int, bool>>(body, n);
  6: var originalDelegate = exprNumbersSmallerThan25.Compile();
  7: List<int> numbersSmallerThan25 = numbers.Where(originalDelegate).ToList();


Actually this is exactly what internally is going on. Of course you would use lambda expressions because obviously it’s a more clear and shorter way to define an expression.

Expression tree viewer

To visualize expression trees, you can use the Expression Tree Visualizer tool that is part of the Visual Studio 2008 CSharp samples. To use it from Visual Studio, you have to build it first and copy the required DLL so that Visual Studio can use the visualizer:

  • First choose Help | Samples from the Visual Studio 2008 menu, click the ‘local Samples folder’ link and unzip the CSharpSamples/LinqSamples/ExpressionTreeVisualizer solution. 
  • Build the solution and then copy the bin\debug\ExpressionTreeVisualizer.dll assembly to the ‘Visual Studio 2008\Visualizers’ directory (to find out the directory on your system, select  Tools | Options | Projects and Solutions | General  from the Visual Studio menu)
  • Restart Visual Studio

You can now test the visualizer by debugging your test project, hovering your mouse above an expression variable and clicking the magnifying glass:

lambda02

Click the magnifying glass to view the expression tree:

lambda03

Why use expression trees?

Expression trees provide an efficient data representation of lambda expressions (which are functions, ultimately) – so they are data structures that represent executable code. You can use them to interact with data at a low level: they can be evaluated, parsed and changed during runtime, and transformed to IL code. But why would we want to do such conversions?

To take an example, LINQ2SQL makes heavy use of expression trees. The following is a very simple example of a LINQ2SQL expression:

  1: var employees = from e in db.Employees
  2:                 where e.City == "Brussels"
  3:                 select e;

What happens is that this expression is converted into an data structure (expression tree), and then this expression tree is analyzed by the LINQ compiler to decide the best way to convert it to SQL statements, which will be something like:

  1: SELECT [t0].[City], [t0].[EmployeeName]
  2: FROM [db0].[Employees] AS [t0]
  3: WHERE [t0].[City] = @p0

So it’s nothing more than a transformation from expresssion -> data structure -> string representation. The goal of these transformation steps is to generate a data structure that is abstract, so that another process can handle it: the LINQ compiler can analyze and parse it in any way it wants (optimized for data access), or you can pass the same expression tree to some other piece of code that transforms it to something complete different, optimized for something else.

May 6, 2010 06:42 by lustuyck
E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Tips and tricks (2)

The ?? operator is called the null-coalescing operator and is not very well-known, but can be very useful sometimes to define default values for types as in the following example:

  1: int? number1 = null;
  2: // number2 = number1, unless number1 is null, in which case number2 = -1
  3: int number2 = number1 ?? -1;

 

If number1 is null then number2 will be set to -1, if number1 is not null, number2 will get the value of number1. A useful case where this can be applied is in properties, to guarantee that a collection is initialized:

  1: private List<int> numbers = null;
  2: public List<int> Numbers
  3: {
  4:   get { return numbers ?? new List<int>(); }
  5: }


You can even do a number of comparisons in one line of code:

  1: DateTime? dateTime1 = null;
  2: DateTime? dateTime2 = null;
  3: DateTime? dateTime3 = DateTime.Now;
  4: DateTime dateTime = dateTime1 ?? dateTime2
  5:                     ?? dateTime3 ?? DateTime.MinValue;

 

This obviously avoids a large block of if-then-else statements.

May 6, 2010 05:47 by lustuyck
E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Tips and tricks (1)

The following code is a straightforward example on how to use the event programming model: the main method is the subscriber that will receive ValidationCompleted notifications, and the Validation class is the publisher that publishes the ValidationCompleted event:

  1: class Program
  2: {
  3:   static void Main(string[] args)
  4:   {
  5:     Validation validation = new Validation();
  6:     validation.ValidationCompleted +=
  7:         new Validation.ValidationHandler(validation_ValidationCompleted);
  8:     validation.Execute();
  9:   }
 10:   static void validation_ValidationCompleted(object sender,
 11:                                                      bool succeeded)
 12:   {
 13:     Console.WriteLine(string.Format("Validation result: {0}", succeeded));
 14:   }
 15: }
 16: 
 17: public class Validation
 18: {
 19:   public delegate void ValidationHandler(object sender, bool succeeded);
 20:   public event ValidationHandler ValidationCompleted;
 21:   public void Execute()
 22:   {        
 23:     // do some validation here
 24:     bool validationSucceeded = true;
 25:     SignalValidationCompleted(validationSucceeded);
 26:   }
 27:   protected void SignalValidationCompleted(bool succeeded)
 28:   {
 29:     if (ValidationCompleted != null)
 30:     {
 31:       ValidationCompleted(this, true);
 32:     }
 33:   }
 34: }

 

If you run this, the result is what you expect:

TipsTricks01


Notice in the Validation publisher class the test to check for null:

  1: if (ValidationCompleted != null)
  2: {
  3:   ValidationCompleted(this, true);
  4: }

 

You have to do this because when there are no subscribers, the ValidationCompleted event will be null.

A nice trick to avoid the need for this null-test, is to add an empty delegate to the event declaration:

  1: public event ValidationHandler ValidationCompleted = delegate {};

 

This way, the event will never be null and you can just call it:

  1: protected void SignalValidationCompleted(bool succeeded)
  2: {
  3:   ValidationCompleted(this, true); // No null check needed anymore
  4: }

 

A nice little trick.

May 6, 2010 05:45 by lustuyck
E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

I love lambda expressions (part 1)!

In the beginning, you are nervous, not sure and even afraid. Slowly you get used to it by experimenting and finally you can’t live without it anymore. To avoid confusion, I’m talking about lamda expressions here.  To prove my point, let’s first go back into time.

If we would need to filter out a list of numbers to get all the numbers smaller than 25, following code does that fine:

  1: List<int> numbers = new List<int> { 1, 7, 16, 23, 41, 66 };
  2: List<int> numbersSmallerThan25 = new List<int>();
  3: foreach (int number in numbers)
  4: { if (number < 25) { numbersSmallerThan25.Add(number); } }

Using predicate delegates

Of course, we can do a lot better, by creating a method that checks if a passed number is smaller than 25, and passing this method as a parameter to the FindAll method of the list:

  1: static void Main(string[] args)
  2: {
  3:    List<int> numbers = new List<int> { 1, 7, 16, 23, 41, 66 };
  4:    List<int> numbersSmallerThan25 =
  5:                     numbers.FindAll(MatchNumberSmallerThan25);
  6: }
  7: private static bool MatchNumberSmallerThan25(int number)
  8: {
  9:   return number < 25;
 10: }

Here we make use of predicate delegates. A predicate delegate is a method that returns true or false. In this case we define a MatchNumberSmallerThan25 method, which returns true if the passed number is smaller than 25. Now we can pass this predicate to the FindAll method of the generic list. As a result, the FindAll method executes the MatchNumberSmallerThan25  method for each item in the generic list. For each call that returns true, the collection item is added to the returning collection of the FindAll method.

Using anonymous methods

This already seems a lot better, however, we still have to create private methods for every case we have, methods that we probably never reuse. So we could improve this by using anonymous methods:

  1: List<int> numbers = new List<int> { 1, 7, 16, 23, 41, 66 };
  2:  List<int> numbersSmallerThan25 = numbers.FindAll(
  3:                 delegate(int number) { return number < 25; } );

In this example we are passing an anonymous inline piece of code as a predicate delegate to the FindAll method, which does exactly the same as the MatchNumberSmallerThan25 method. This offers us a number of advantages: no need to clutter your code with private named methods, the code is where it is used, no need to specify the return type (it is inferred from the signature of the delegate type to which the anonymous method is being cast to) and you can refer to the outer variables from within the anonymous method.

Using lambda expressions

As the title of this blog entry suggests, we still have a way to improve the code above. So let’s dot that:

  1: static void Main(string[] args)
  2: {
  3:   List<int> numbers = new List<int> { 1, 7, 16, 23, 41, 66 };
  4:   List<int> numbersSmallerThan25 = numbers.FindAll(n => n < 25);               
  5: }  

Did you notice the amount of code we have left? And this is all thanks to the beloved lambda expressions! At first it seems a bit cryptic, so what is a lambda expression? It’s nothing more than an anonymous method used to create a delegate, and that takes takes the form:

argument-list => expression

In our example, the lambda expression consists of an argument named ‘n’ (which is implicitely typed as an integer), then the lambda operator ‘=>’, followed by an expression ‘n < 25′.

You could read it as: ‘There’s a method that takes one parameter n and a list of integers; and it returns every number from that list that is smaller than 25 ’. This lambda expression is then passed to the FindAll method of the generic collection. Actually, this lambda expression is a very small, compact, anonymous method that is another way of writing an anonymous method, which is also more readable and verbose.

A closer look

If we look at the FindAll signature, we see that it expects a parameter of type Predicate<T>, and because we are applying it to a list of integers, it has to be of type Predicate<int>. So we could explicitely create and use such predicate as follows:

  1: List<int> numbers = new List<int> { 1, 7, 16, 23, 41, 66 };
  2: Predicate<int> predNumbersSmallerThan25 = n => n < 25;
  3: List<int> numbersSmallerThan25 = numbers.FindAll(predNumbersSmallerThan25);

However, FindAll is a .NET 2.0 method, and the preferred way is using the .NET 3.5 Where method, which expects a Func<T1, TResult> parameter. T1 is the type of the parameter (which is int) and TResult is the type of the returned value (which is bool). So we end up with the following code, which has exact the same results as the previous code:

  1: List<int> numbers = new List<int> { 1, 7, 16, 23, 41, 66 };
  2: Func<int, bool> funcNumbersSmallerThan25 = n => n < 25;
  3: List<int> numbersSmallerThan25 =
  4:                numbers.Where(funcNumbersSmallerThan25).ToList();

Other examples

Now that you have fallen in love with lambda expressions too, some other examples:

  1: // Add 5 to every number in the collection
  2: List<int> numbersAdded5 = numbers.ConvertAll(n => n + 5);
  3: // Sort list descending (lambda expression with two arguments!)
  4: numbers.Sort( (a,b) => b-a );
  5: // Groups collection of numbers into two groups containing
  6: // odd or even numbers
  7: var grouped = numbers.GroupBy(n => n%2==0);

Note the ‘var’ keyword in the last example. It means that the compiler will infer the type for you, it does not mean that it’s some kind of variant data type that can change. In this case, the grouped variable will always be of type ‘System.Linq.GroupedEnumerable<int,bool,int>’, because that is the return type of the GroupBy method.

May 6, 2010 05:40 by lustuyck
E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Create and initialize objects and collections in one step in C# 3.0

Imagine you have the following class:

  1: public class Customer
  2: {
  3:   public string FirstName { get; set; }
  4:   public string LastName { get; set; }
  5:   public string Street { get; set; }
  6:   public string City { get; set; }
  7: }

Traditionally, when you need to create Customer objects and add them to a collection, you would do it like:

  1: List<Customer> customers = new List<Customer>();
  2: Customer customer1 = new Customer();
  3: customer1.FirstName = "Homer";
  4: customer1.LastName = "Simpson";
  5: customer1.Street = "Evergreen terrace";
  6: customer1.City = "Springfield";
  7: customers.Add(customer1);
  8: Customer customer2 = new Customer();
  9: customer2.FirstName = "Marge";
 10: customer2.LastName = "Simpson";
 11: customer2.Street = "Evergreen terrace";
 12: customer2.City = "Springfield";
 13: customers.Add(customer2);

Obviously, this is a lot of work. Fortunately, in C# 3.0 there’s a way to create and initialize objects and collections in one step, using the object initializers feature:

  1: List<Customer> customers2 = new List<Customer>()
  2: {
  3:   new Customer() { FirstName = "Homer", LastName = "Simpson",
  4:                    Street = "Evergreen terrace", City = "Springfield" },
  5:   new Customer() { FirstName = "Marge", LastName = "Simpson",
  6:                    Street = "Evergreen terrace", City = "Springfield" }
  7: };

Much cleaner, isn’t it?

May 6, 2010 05:15 by lustuyck
E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed