Partager via


Tuples and Anonymous Types

[Blog Map]  [Table of Contents]  [Next Topic]

Anonymous types are a way that you can define, declare, and instantiate a type and an object in a single step.

This blog is inactive.
New blog: EricWhite.com/blog

Blog TOCSelection in a LINQ query expression is an operation of projection.  While projecting, you often want to create new types that are used only in the context of projection.  After you have processed the projection, you are not interested in using the types again.  Anonymous types allow you to define a class, and declare, and initialize an object of that class without giving the class a name.

Anonymous types are one of C# implementations of the idea of a tuple.  In computing, a tupple is a set of name/value pairs.  For example, the contents of an address in the Typical Purchase Order XML document could be expressed as:

Name: Ellen Adams
Street: 123 Maple Street
City: Mill Valley
State: CA
Zip: 90952
Country: USA

The term tuple originated from the sequence single, double, triple, quadruple, quintuple, n-tuple.  When you create an instance of an anonymous type, it is convenient to think of it as creating a tuple of order n.  If you write a query that creates a tuple in the select clause, then the query returns an IEnumerable of the tuple.

Projection

You often have one set of data and want to create another set of data from it.  You may want to see a subset of the fields.  The data may be filtered so that there is less of it.  The data may be combined with other data to create an entirely new shape of data.  This is called projection.

When you project data, one approach that you can take is to define a new type, and then declare an object (or collection of objects) to hold your projection.  However, often the only purpose to the newly defined type is to hold the projection.  After you have finished processing the projection, you are no longer interested in the type.  It is fairly inconvenient to define the new type just to hold data for a short while.  One interesting solution to this problem is to use an anonymous type.

You create anonymous types using the C# 3.0 object initializer syntax.

A Simple Example

The following shows a very simple example of the creation of an anonymous type:

var x = new
{
Name = "Eric",
Phone = "555-1212"
};
Console.WriteLine("x.Name:" + x.Name);
Console.WriteLine("x.Phone:" + x.Phone);

When you use an object initializer in this fashion, the C# compiler automatically generates a class with two public properties, Name and Phone.  The compiler infers the types of the two properties.  In this case, they are both of type string.

There is nothing so very special about this type - it is simply an instance of a reference type.  The only thing different about this type is that it has a name that you can't see.

This is a situation where it is required to use the var keyword.  You can't see the name for the type.  But by using the var keyword, we can let the compiler infer the type.

Rest assured that the variable is still strongly typed.  For example, you can't assign it to a variable of an incompatible type.

An Anonymous Type used for Projection

A typical projection would be as follows.  If you have a class Customer (defined using the C# 3.0 automatic properties syntax):

public class Customer
{
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
}

You could create an anonymous type from this customer, as follows:

Customer c = GetCustomer();
var x = new
{
Name = c.Name,
Phone = c.Phone
};

However, you can be even terser.  C# 3.0 supports a projection style object initializer.  If you want the anonymous type to have the same property names as the class from which you are projecting, then you can omit the assignment, like so:

Customer c = GetCustomer();
var x = new
{
c.Name,
c.Phone
};

This is permitted so long as the property expression in the initializer ends in a name.  If the property expression does not end in a name, then you must use the syntax "PropName = expr".  The following is not permitted:

var z = new {
c.Name,
"425-555-1212"
};

Instead, you would have to write:

var z = new
{
c.Name,
Phone = "425-555-1212"
};

Projection of a Collection

When coding in the functional style, you will often take one collection of a type, and project a new collection of a different type.  Consider the following code, which takes a collection of Customer objects, and projects an anonymous type.  The code uses the Select extension method, which is what we use when we want to project a collection of a different type.  The following code is attached to this page:

public class Customer
{
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
}

class Program
{
static void Main(string[] args)
{
Customer[] custList = {
new Customer {
Name = "Bob",
Address = "123 Main Street, Seattle, WA 98111",
Phone = "555-1234"
},
new Customer {
Name = "Bill",
Address = "555 Center Street, Tacoma, WA 97158",
Phone = "555-9999"
}
};

var newCustList =
custList.Select(
c => new
{
UCName = c.Name.ToUpper(),
UCAddress = c.Address.ToUpper()
}
);

foreach (var c in newCustList)
Console.WriteLine(c.UCName);
}
}

Scoping of Anonymous Types

One point that is important to make is that anonymous types (and collections of them) are limited to local scope.  This, of course, should be clear based on the semantics of C# - if you want to return an object from a method, you must declare the method to return an object of that type.  But if you are using an anonymous type, you can't declare the method to return such a type.  Once you want to use an object outside of a method, you must make it a nominal type.

If you have an object (or collection of objects) of an anonymous type that you want to use outside of a method, the approach to take is to define a nominal type with appropriate scope, and rewrite your code to project your nominal type.

The C# specification states that if you have two instances of an anonymous type such that all of their properties are named the same, the properties are in the same order, and the types of each of the properties are identical, then the two anonymous objects have the same type.  You can use this characteristic of anonymous types to implement some useful idioms in local scope.  For instance, this allows you to create two queries that both result in a collection of anonymous types, and if they are the same, you can concatenate the results.  The following code shows this.  The code is attached to this page.

int[] collection1 = new[] { 1, 2, 3 };
int[] collection2 = new[] { 4, 5, 6 };

var q1 =
from i in collection1
select new
{
FromCollection = 1,
Value = i
};

var q2 =
from i in collection2
select new
{
FromCollection = 2,
Value = i
};

var q3 = q1.Concat(q2);

foreach (var v in q3)
Console.WriteLine(v);

Using the facility that anonymous types have a pretty good ToString method, this example produces the following output:

{ FromCollection = 1, Value = 1 }
{ FromCollection = 1, Value = 2 }
{ FromCollection = 1, Value = 3 }
{ FromCollection = 2, Value = 4 }
{ FromCollection = 2, Value = 5 }
{ FromCollection = 2, Value = 6 }

There are techniques where you can take advantage of knowledge of the compiler implementation to allow anonymous types to escape from local scope.  I've seen such types hanging out in lower downtown Seattle.  They start drinking wine in the morning, smoking crack, and mugging people.  For their own good (and ours), keep them in local scope.

[Blog Map]  [Table of Contents]  [Next Topic]

TuplesAndAnonymousTypes.cs

Comments

  • Anonymous
    June 28, 2007
    "The above is commonly referred to as a 5-tuple." If I am not wrong, it should be "6-tuple". BTW... Thank you very much for thsi tutorial. Very helpful. Cheers, Pravin

  • Anonymous
    August 24, 2007
    " If the property expression does not end in a name, then you must use the syntax "PropName = expr". The following would not be permitted:" Can we see an example of a property expression that doesn't end in a name? michael.isbell@gmail.com

  • Anonymous
    January 21, 2008
    The comment has been removed

  • Anonymous
    January 22, 2008
    I understand that it is possible to get anonymous types to leak out of local scope. However, my new favorite approach is to use classes that have a certain number of automatic properties. The syntax is minimal - if you have a 6-tupple, it takes 8 lines of code. Further, you have the option of writing a constructor if you need one for some reason or other. And lastly, it is strongly typed. I used this approach in my most recent prototypes of a LINQ to XML programming interface to Open XML documents, elsewhere on this blog.

  • Anonymous
    July 20, 2008
    In a previous post, I introduced Get-RecordedTV , which was built upon another function, Search-WindowsDesktop

  • Anonymous
    October 08, 2009
    The comment has been removed

  • Anonymous
    October 11, 2009
    Hi Tegiri, One piece of background about LINQ is that it was intended to be somewhat similar to SQL, although the mechanism is completely different, and there are important differences in capabilities and syntax.  I think it was felt that similarity to SQL would provide some level of familiarity to the existing community of SQL developers.  And in the case of LINQ to SQL and LINQ to Entities, the similarities to SQL prove even more valuable. This is why they chose to use Select as the projection operator.  Just to be clear about what I mean: projection is the operation of taking some collection of type T, and 'projecting' a new collection of type T1.  In the case of elaborate queries, the collection of type T1 is then 'projected' into a collection of type T2, and so on. In contrast, joining is the operation of taking two related data sources, and 'projecting' a single collection from the two. To use your example, projecting a collection of string lengths from a collection of strings can be accomplished like this: var strings = new[] { "a", "abc" }; var stringLengths = strings.Select(s => s.Length); Does this make sense? -Eric

  • Anonymous
    July 26, 2010
    The comment has been removed