12

In C#, I would like to figure out if it's possible to declare an anonymous type where the fields are not known until run-time.

For example, if I have a List of key/value pairs, can I declare an anonymous type based on the contents of that list? The specific case I'm working with is passing parameters to Dapper, where I don't know ahead of time how many parameters I will have.

List<Tuple<string, string>> paramList = new List<Tuple<string, string>>() {
    new Tuple<string, string>("key1", "value1"),
    new Tuple<string, string>("key2", "value2")
    ...
};

I'd like to convert this List (or an equivalent Map) into an anonymous type that I can pass to Dapper as query parameters. So ideally, the above list would wind up looking like this, if defined as an anonymous type:

new { key1=value1, key2=value2, ... }

I've seen several questions on StackOverflow asking about extending anonymous types after they are declared ("extendo objects"), or declaring arbitrary fields on an object after it's created, but I don't need to do that... I just need to declare the types dynamically up-front once. My suspicion is that it will require some fancy reflection, if it's possible at all.

My understanding is that the compiler defines a type for anonymous classes under the hood at compile-time, so if the fields of that class are not available until run-time, I might be out of luck. My use case may in fact be no different in actuality than using an "extendo object" to define arbitrary fields, whenever.

Alternatively, if anyone knows of a better way to pass query parameters to Dapper (rather than declaring an anonymous class), I would love to hear about that as well.

Thanks!

UPDATE

Sorry for the delay in getting back to this one! These answers were all great, I wish I could give points to everyone. I ended up using jbtule's solution (with edit by Sam Saffron), passing IDynamicParameters to Dapper, so I felt I had to give the answer to him. The other answers were also good, and answered specific questions that I had asked. I really appreciate everyone's time on this!

Egahn
  • 369
  • 1
  • 4
  • 13

3 Answers3

14

Dapper's creators were very aware of this problem. This kind of functionality is really needed for INSERT and UPDATE helpers.

The Query, Execute and QueryMultiple methods take in a dynamic parameter. This can either be an anonymous type, a concrete type or an object that implements IDynamicParameters.

public interface IDynamicParameters
{
    void AddParameters(IDbCommand command, Identity identity);
}

This interface is very handy, AddParameters is called just before running any SQL. Not only does this give you rich control over the parameters sent to SQL. It allows you to hook up DB specific DbParameters, since you have access to the command (you can cast it to the db specific one). This allows for support of Table Values Parameters and so on.

Dapper contains an implementation of this interface that can be used for your purposes called DynamicParameters. This allows you to both concatenated anonymous parameter bags and add specific values.

You can use the method AddDynamicParams to append an anonymous type.

var p = new DynamicParameters();
p.AddDynamicParams(new{a = "1"});
p.AddDynamicParams(new{b = "2", c = "3"});
p.Add("d", "4")
var r = cnn.Query("select @a a, @b b, @c c, @d d", p);
// r.a == 1, r.b == 2, r.c == 3, r.d == 4
qujck
  • 14,388
  • 4
  • 45
  • 74
jbtule
  • 31,383
  • 12
  • 95
  • 128
12

In C#, I would like to figure out if it's possible to declare an anonymous type where the fields are not known until run-time.

Anonymous types are generated by the compiler. You want to know if the compiler will generate you a compiler-generated type with field types not known to the compiler. Clearly it cannot do so; as you correctly surmise, you are out of luck.

I've seen several questions on StackOverflow asking about extending anonymous types after they are declared ("extendo objects")

We usually call those "expando" objects.

If what you want to do is make an expando object based on a dictionary of key-value pairs, then use the ExpandoObject class to do that. See this MSDN article for details:

http://msdn.microsoft.com/en-us/magazine/ff796227.aspx

If what you want to do is generate a bona-fide .NET class at runtime, you can do that too. As you correctly note, you need some fancy reflection to do so. What you want to do is make a collectible assembly (so-called because unlike a normal assembly, you generate it at runtime and the garbage collector will clean it up when you are done with it.)

See http://msdn.microsoft.com/en-us/library/dd554932.aspx for details on how to make a collectible assembly and emit a type into it using a TypeBuilder.

Dan J
  • 16,319
  • 7
  • 50
  • 82
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Sorry for the confusion, "ExpandoObject" is what I meant to type =) I am not yet sure which of your suggested options will work for me, I need to do some experimentation with passing different objects into Dapper, and see what works and what doesn't. RE: your first paragraph, I think I had pretty much convinced myself it wouldn't work by the time I finished typing out my question! You laid it out in a very clear fashion, thank you! =) – Egahn Sep 15 '11 at 20:48
  • 2
    The trouble with Expandos and dapper is that there is no internal special handling for them so the params will not be extracted from the underlying IDictionary or whatever. That is why in this context you probably want to use `IDynamicParameters` @Egahn ... you can meld and generate a new type out of 2 anon types using reflection.emit but in this context you do not need to. – Sam Saffron Sep 16 '11 at 11:26
7

You can't use an anonymous type. Anonymous types are generated by the compiler rather than at run-time. You could certainly use dynamic though:

dynamic dynamicObj = new ExpandoObject();    
var objAsDict = (IDictionary<String, Object>)dynamicObj;

foreach(var item in paramList)
{
    objAsDict.Add(item.Item1, item.Item2);
}

You can then use dynamicObj as a regular object:

Console.WriteLine(dynamicObj.key1); // would output "value1"
Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
  • Fascinating, I did not know you could do that. I'll have to experiment with doing this and passing the resulting object into Dapper to see if it works. The problem I foresee is, I believe the parameter name in the SQL query string that I pass into Dapper determines what field it attempts to access on the parameters object I also pass into Dapper... so if my SQL query has a parameter like "@A" in it, the parameters object I pass in needs a field called "A". I'll give this a try though. – Egahn Sep 15 '11 at 20:44
  • I'm not in front of my computer, but how does that work? Wouldn't you have to at least cast it to a DICTIONARY not an IDICTIONARY? I don't think there is any chance that casting it to an interface would magically IMPLEMENT the add method. I understand it would compile, but it won't actually add it to the collection right? – John Buchanan Sep 15 '11 at 21:29
  • @John Buchanan - ExpandoObject implements IDictionary. IDictionary contains the Add(TKey, TValue) method. Therefore, once you cast to IDictionary...Add works. – Justin Niessner Sep 15 '11 at 21:55
  • @Justin Niessner - Sorry for my ignorance, but why the cast? Since ExpandoObject implements IDictionary, why not just dynamicObj.Add()? – John Buchanan Sep 16 '11 at 01:38