星期日, 九月 09, 2007

Overcoming the Limitations of Anonymous Types



 
 

Sent to you by Hudong via Google Reader:

 
 

via MSDN Blogs by Jeremiah.Clark on 9/7/07

It seems clear that the new anonymous types feature for C# 3.0 is going to be very useful, however there are a few limitations that come along with them: limited scope and read-only properties.

Anonymous types have a method scope. This means that if you pass an anonymous type outside the containing method boundary, then you have to cast it as an object. For example:

static void ContainingMethod()
{
// Create an anonymous type
var anondata = new
{
IntegerVal = 1,
DoubleVal = 2.0D,
DateTimeVal = DateTime.Now,
StringVal = "some string"
};

// Pass the type to a method
ExternalMethod(anondata)
}

static void ExternalMethod(object data)
{
// We can't access the properties and
// we can't cast it to anything of use
// because it is anonymous
data.(???)
}

We can't access any of the properties of the type because it has been cast to an object. So, let's see what the type looks like to the external method (just a small change to the second method from above):

static void ExternalMethod(object data)
{
Console.WriteLine(data.GetType());
}


This outputs something like:

<>f__AnonymousType0`4[System.Int32,System.Double,System.DateTime,System.String]

This is the symbol that the compiler picked for the anonymous type that we declared. There are a few interesting bits of information here. The "0" after "<>f__AnonymousType" is a zero-based index that represents each unique anonymous type definition. In our case, this is the first anonymous type that has been defined within the assembly. The next number, "4", represents the number of properties that they type contains. Following that is a list of the types for the properties.

This is interesting because the compiler will reuse anonymous types that match the property declarations (in the same order). To the compiler, these will be of the exact same type:

var car = new
{
Make = "Ford",
Price = 35000.54,
Year = 2006
};

var person = new
{
Name = "Bob Smith",
Weight = 195.5,
Age = 30
};

What if we need to use the anonymous type outside of the containing method? Well, the best practice would be to create a standard named class and use that instead of the anonymous type. But what if you really need (or just want) to pass an anonymous type and access the properties from outside the containing method? Well, all you need is a little reflection (again using the example from above):

static void ContainingMethod()
{
var anondata = new
{
IntegerVal = 1,
DoubleVal = 2.0D,
DateTimeVal = DateTime.Now,
StringVal = "some string"
};

ExternalMethod(anondata);
}

static void ExternalMethod(object data)
{
// Get the type that was passed in
Type t = data.GetType();
// Get a list of the properties
PropertyInfo[] piList = t.GetProperties();
// Loop through the properties in the list
foreach (PropertyInfo pi in piList)
{
// Get the value of the property
object o = pi.GetValue(data, null);
// Write out the property information
Console.WriteLine("{0} ({1}): \t{2}", pi.Name, o.GetType(), o.ToString());
}
}

This outputs:

IntegerVal (System.Int32): 1
DoubleVal (System.Double): 2
DateTimeVal (System.DateTime): 9/7/2007 8:06:04 PM
StringVal (System.String): some string

As you can see, by using reflection we can access the properties of the anonymous type from another method or class. Again, it would probably be better to just write a named class to do this, but this example is intended to show that it is possible and fairly easy to pass data using only anonymous types.

Another limitation of anonymous types is that the properties are read-only. I really don't see many instances where you would need to change the values inside of an anonymous type, but if you ever wanted to there is a way to do that as well. And you guessed it, it involves using reflection again:

static void ContainingMethod()
{
// Create the anonymous type
var anondata = new
{
IntegerVal = 1,
DoubleVal = 2.0D,
DateTimeVal = DateTime.Now,
StringVal = "some string"
};

// This is the underlying private field name
// for the IntegerVal property
string fieldName = "<IntegerVal>i__Field";

// Get the type
Type t = anondata.GetType();
// Get the field information using reflection
FieldInfo fi = t.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
// Set the field using reflection
fi.SetValue(anondata, 1234);

// Output the value from the original anonymous type property
Console.WriteLine("IntegerVal = {0}", anondata.IntegerVal.ToString());
}

This outputs:

IntegerVal = 1234

The interesting thing here is that we cannot set the read-only property using reflection because there is no setter. But we can change the underlying private field for the property. The field name seems to follow the convention "<PropName>i__Field" where 'PropName' is the name that we used for the property. This name can be gathered by using reflection and enumerating the fields (using the GetFields method of the Type object). So as you can see, we can change the value of the property of an anonymous type if we really need to.

To sum this up, using reflection we can do some pretty cool things with anonymous types. For most scenarios it would be easier and more practical to create named classes to pass and alter data, but this is meant to show that we can break the rules a little if we really want to.


 
 

Things you can do from here:

 
 

没有评论:

发表评论