Horrible grotty hack: returning an anonymous type instance

One of the reasons I don’t view anonymous types as being too bad is that they’re nicely confined to methods. You can’t declare the type that you’re returning from a method if it’s anonymous (or if one of its type arguments is generic, e.g. a List<T> where T is an anonymous type and T isn’t a type parameter to the method itself). However, you can get around this if you’re sneaky.

I’ve always known that it’s perfectly easy to return an instance of an anonymous type by declaring that the method will return object. However, it hadn’t occurred to me before today that you can actually cast back to that type afterwards. Of course, you can’t just use a normal cast expression – that requires the name of the type to be known at compile-time. But you can do a cast in a generic method… and you can use type inference to supply a type argument… and two anonymous type instance creation expressions will use the same type within the same assembly if the order, names and types of the properties are the same.

Behold the evil cheesecake factory!

using System;

static class GrottyHacks
{
    internal static T Cast<T>(object target, T example)
    {
        return (T) target;
    }
}

class CheesecakeFactory
{
    static object CreateCheesecake()
    {
        return new { Fruit=“Strawberry”, Topping=“Chocolate” };
    }
   
    static void Main()
    {
        object weaklyTyped = CreateCheesecake();
        var stronglyTyped = GrottyHacks.Cast(weaklyTyped,
            new { Fruit=“”, Topping=“” });
       
        Console.WriteLine(“Cheesecake: {0} ({1})”,
            stronglyTyped.Fruit, stronglyTyped.Topping);           
    }
}

The important thing to note here is that the stronglyTyped variable really is of the same anonymous type as the one used in the CreateCheesecake method. When we use the Fruit and Topping properties in the last statement, that’s checked at compile-time.

Of course, it all goes pear-shaped if you make the slightest of errors when giving the Cast method an example of what you want to cast to – if you got the order of the properties wrong, for example, the code would still compile, but the cast would throw an exception at execution time.

How useful is this? Ooh, probably not at all. Please do not use this “technique” in your code. If you do, at least don’t mention my name anywhere near it. It’s all fun though, isn’t it?

20 thoughts on “Horrible grotty hack: returning an anonymous type instance”

  1. I saw somebody on SO discussing using this approach in ASP.NET MVC to share data between the controller and view (rather than either using the dictionary, or a formal class). It made me shudder.

    One of the other big problems I see is that if you change the CreateCheesecake code to include {…, Base=”Biscuit”}, then your other (Main) code breaks suddenly, as without the Base definition it is a different type.

  2. Oh, forgot to say; if you use a `Func` instead of a `T` in the last arg, you don’t actually create an object, and the func gets cached anyway (and is never used) – i.e.

    var foo = Cast(bar, ()=>new {…});

    Probably not hugely better… I used a similar approach in a sample a while ago to create an empty `List` of an anon-type (and then drop items in later, but in the same method) – i.e.

    List CreateList(Func template) {…}

  3. Yes, its only fun and not intended for production use, but I made and then publish on my blog some time before extensions methods for this a for returning anonymous type from mthe method in another assembly. As I say, only for fun and study. :)

    namespace LINQAnonymous
    {
    ///

    /// Rozšíření pro LINQ
    ///

    static class RSLinqExtensions
    {

    ///

    /// Metoda přetypuje objekt na anonymní typ, jehož struktura byla předána v parametru ///

    /// Kompilátorem odvozený anonymní typ
    /// Prototyp se strukturou anonymního typu
    /// Instanci anonymního typu, nebo null, jestliže konverzi nelze provést
    /// Metoda se pokusí převést data z různých assembly
    public static T ToAnonymousType(this object obj, T prototype)
    where T: class
    {

    T atiObj = obj as T;

    if (atiObj == null)
    {

    atiObj = GetTypeInstance(obj, prototype.GetType()) as T;

    }

    return (atiObj);
    }

    private static object GetTypeInstance(object obj, Type expected)
    {
    object atiObj = null;

    ConstructorInfo constructorInfo = expected.GetConstructors()[0];

    if (constructorInfo == null)
    {
    return null;
    }

    ParameterInfo[] paramInfos = constructorInfo.GetParameters();
    PropertyInfo[] origProperties = obj.GetType().GetProperties();

    if (paramInfos.Count() != origProperties.Count())
    {
    return null;
    }

    object[] paramArgs = new object[paramInfos.Count()];

    for (int i = 0; i < paramArgs.Length; i++)
    {
    PropertyInfo origProperty = origProperties.Where(prop => prop.Name == paramInfos[i].Name).FirstOrDefault();

    if (origProperty == null)
    {
    return null;
    }

    object val = origProperty.GetValue(obj, null);
    if (origProperty.PropertyType != paramInfos[i].ParameterType)
    {
    val = GetTypeInstance(val, paramInfos[i].ParameterType);
    }

    paramArgs[i] = val;
    }

    atiObj = constructorInfo.Invoke(paramArgs);
    return atiObj;
    }
    ///

    /// Metoda vrátí
    ///

    /// Kompilátorem odvozený anonymní typ
    /// Prototyp se strukturou anonymního typu
    /// List instancí anonymního typu, nebo null, jestliže konverzi nelze provést
    /// Metoda se pokusí převést data z různých assembly
    public static List CastToList(this object obj, T prototype)
    where T : class
    {
    List
    list = new List();
    IEnumerable
    enumerable = obj as IEnumerable;

    if (enumerable != null)
    {
    list.AddRange(enumerable);
    }
    else
    {
    IEnumerable enumObjects = obj as IEnumerable;
    if (enumObjects == null)
    {
    return null;
    }

    foreach (object enumObject in enumObjects)
    {
    T currObject = ToAnonymousType(enumObject, prototype);
    if (currObject == null)
    {
    //K čistění listu by neměl být důvod, ale garantujeme, že nevrátíme částečně naplněný list
    list.Clear();
    return list;
    }

    list.Add(currObject);
    }

    }

    return list;
    }
    }

    And using

    //TestAT.GetResult is in the another assmbly
    //Anonymní typ z jiné assembly!
    var result2 = TestAT.GetResult().CastToList(new {FirstLetter = default(char),
    Index =default(int),
    Original = default(string),
    InnerAT = new { X = default(char), B = new { A = default(int) } }
    })
    ;
    foreach (var res in result2)
    {
    Console.WriteLine(res.FirstLetter);
    Console.WriteLine(res.Original);
    }

    Console.WriteLine(TestAT.
    GetResult().
    CastToList(new
    {
    FirstLetter = default(char),
    Index = default(int),
    Original = default(string),
    InnerAT = new { X = default(char), B = new { A =default(int)} }
    }
    ).
    Where(car => car.FirstLetter == ‘T’)
    .FirstOrDefault()
    .ToString());
    Console.ReadLine();

  4. @Blake: I suspected it wasn’t original. I didn’t realise that Tomas had an example though. Doh! I should probably be reading his blog, given my current involvement in his book…

  5. @serhat: Firstly, strings *are* references (and not primitives). Secondly, it shouldn’t be a problem. Either create a new instance, or cast a null reference to the right type for the value. If you use Marc’s idea of a Func then it’ll never be actually created anyway.

  6. This process is even more complicated for VB code. VB supports the notion of both mutable, immutable anonymous types and mix mode anonymous types. (C# anonymous types are immutable by default). Any property in a VB anonymous type can be made immutable by using the “Key” prefix

    Dim x = new With { Key .Name = “foo” }

    In order to cast to an anonymous type, the target type used must get the immutablitiy state (is that the correct phrase?) correct because different types are generated for each state.

    In VB though, it’s often simpler to access the property via late binding. No it’s not safe, but it’s as safe as doing the cast trick. In both circumstances you are doing a non-compile type assertion that a particular object has a particular type.

    Overal experience for either language though is messy. I’ve almost completely abandoned the explicit use of anonymous types in favor of Tuples.

  7. On the subject of creating typed null references to trigger type inference, or define an anonymous type signature, without instantiating anything, I prefer:

    default(string)

    to

    (string) null

    obviously doesn’t reduce typing at all, but it feels less like a hack. The parallel to typeof(string) is quite nice, too.

  8. @James: Yes yes yes! +1 for that. Love it. Will use it next time I need it (which is incredibly rarely). Thanks for the suggestion!

    @Lorenzo: Of course. That’s why it’s fun :)

  9. This is one of those examples where relying on internal implementations can bite you, I imagine – since anonymous types are by design not supposed to leave private scope, the compiler team is free to do whatever they want with the actual implementation.

    Thanks for the brain-twitch, though, Jon. =)

  10. @Erik: No, it’s not implementation-dependent. The compiler-generated name is, but the language spec guarantees that two anonymous object creation expressions using the same property names and compile-time types within the same assembly will create instances of the same type. See section 7.5.10.6 of the C# 3.0 spec.

  11. Wow. That it an interesting idea.

    My head hurts thinking of the evil, obfuscated, and intentionally confusing code that is possible with this…

  12. It makes sense but when they change internal implementation of anonymous types, nobody who uses it, should be surprised of sudden InvalidCastExceptions… :)

  13. @lubos: That wouldn’t just be an internal implementation change – it would be a *specification* change. There’s nothing here which isn’t guaranteed by the spec.

  14. I added a method to Json.NET that uses this technique last year.

    T JsonConvert.DeserializeAnonymous(string json, T anon)

    It deserializes the JSON into instances of the anonymous type. Hacky but it works :)

Comments are closed.