9

I need to loop over a List<dynamic> objects.

The list's objects all have values, but for some reason, I am not able to access any of the dynamic object fields. Below is a screenshot of my debug window:

enter image description here

There you can see the object contains fields (such Alias, Id, Name, etc).

I tried both casting it to a IDictionary<string, object> and ExpandoObject, to no avail. I did not face such a thing before: failing to access existing fields in a dynamic object when they exist.

What is wrong here?

The code is throwing a Microsoft.CSharp.RuntimeBinder.RuntimeBinderException with a message stating {"'object' does not contain a definition for 'Name'"}.

The list was created adding anonymously-typed objects, like this:

return new List<dynamic>(fields.Select(field => new 
                        { 
                            Id = field.Id, 
                            Alias = field.Alias, 
                            Name = field.Name, 
                            Type = field.Type, 
                            Value = field.Value,
                            SortOrder = field.SortOrder
                        }));

where fields is an ICollection<Field>, a strongly-typed collection.

Veverke
  • 9,208
  • 4
  • 51
  • 95
  • 3
    Does it result in an exception, or just blank fields? Also, posting your code would be useful. – Chris Mantle May 12 '15 at 10:19
  • @ChrisMantle: I don't see what is the value of adding more source. Let's assume I get this from an unknown source and - what matters is that - as the screenshot shows - a given element in the list has fields. And the question is: how to access them ? And yes, I do get a Microsoft.CSharp.RuntimeBinder.RuntimeBinderException. – Veverke May 12 '15 at 10:20
  • What's the message in the `RuntimeBinderException`? It's likely to be something along the lines of '[Some type]' does not contain a definition for '[Some property]' – Chris Mantle May 12 '15 at 10:25
  • @ChrisMantle: added it to the question, thanks. – Veverke May 12 '15 at 10:27
  • Does it work if you continue past the exception? – Chris Mantle May 12 '15 at 10:40
  • @ChrisMantle: I don't understand the question. If the code is inside a try/catch block, the exception will be treated, otherwise, the execution flow will be aborted. In my case, for your question's sake, I have just added a try/catch block, and I return the 'object' does not contain a definition for 'Name' error message (exception.Message). – Veverke May 12 '15 at 10:44
  • 1
    Have a read of http://csharpindepth.com/Articles/Chapter14/DynamicGotchas.aspx. Are the values in `section.Fields` dynamic/anonymous, or are they static types of varying types? – David Arno May 12 '15 at 10:47
  • We would like to know how you have initialized the Fields list as DavidArno just said – Anjani May 12 '15 at 10:52
  • @DavidArno: thanks for the link. Yes, the List is made of an anonymous type, something like `new { Name = ... , Id = ... , etc }`. As with regards to the [article](http://csharpindepth.com/Articles/Chapter14/DynamicGotchas.aspx) you mention and the anonymous types issue, everything is taking place in the same assembly, so I assume this should not be the problem. – Veverke May 12 '15 at 11:05
  • 1
    @Veverke just for testing purposes , since what you wrote seems o'k and should indeed work . change Name property to Test i wan't to see if the Exception : {"'object' does not contain a definition for 'Test'"} will be shown – eran otzap May 12 '15 at 11:30
  • @Veverke Are you able to access other properties except 'Name' on the dynamic object? Because field.Name in the watch window indicating some possible side effects? – Siva Gopal May 12 '15 at 11:52
  • I've tried using code similar to yours (anonymous types in `List` iterated over and read), but it works fine for me. If I make sure the types don't match, it will only fail on the ones that don't match. I guess you'll have to find the smallest possible full code that reproduces the behaviour you're seeing :) Also, what's your .NET FW version? That said, I don't think this is a proper use case for `dynamic`, so perhaps you're going to refactor it anyway? :D – Luaan May 12 '15 at 12:50
  • 1
    Also, your error message `{"'object' does not contain a definition for 'Name'"}` is weird. Normally, it would say sth along lines `{"'<>f_AnonymousType0' does not contain a definition for 'Name'"}` – frost May 12 '15 at 12:52
  • @frost Yeah, I was just about to add that. I tried a dozen ways and couldn't reproduce that error. This is really weird :) – Luaan May 12 '15 at 12:55
  • Oh! Is the anonymous type in a different assembly? Anonymous types are `internal`, so I can see how that would cause it to be "dynamicced" into `object`. – Luaan May 12 '15 at 12:57
  • @Luaan: that's what the article David Arno above posted mentions, and I checked that - the anonymous type is created in the same assembly. – Veverke May 12 '15 at 12:59
  • It's still the only way I managed to gouge the exception to end up referencing `'object'`. Is the anonymous type created inside a type that is `private`, perhaps? One way or another, it *must* be inaccessible to the runtime binder. – Luaan May 12 '15 at 13:04
  • @Luaan: sorry folks, my bad. The anonymous type is indeed defined in a different assembly, and adding InternalsVisibleTo("theCallingAssemblyName") fixes the issue. I wrote above that I checked and everything was being done in the same assembly, but I was wrong. Luaan, please post an answer with that so I can mark it as the answer. – Veverke May 12 '15 at 13:18

1 Answers1

10

The telling part is the exception:

{"'object' does not contain a definition for 'Name'"}.

This indicates that the runtime binder was not actually capable of accessing the type you're passing in dynamic (since dynamic does actually enforce visibility rules).

The most likely cause of this is that you're creating the anonymous type in a different assembly from the one where you're subsequently reading it - since anonymous types are declared internal, the consuming assembly cannot access it, causing the error message above.

Contrast with the usual case of runtime binder exceptions:

'<>f__AnonymousType0< string >' does not contain a definition for 'Name'

EDIT:

A possible solution to the problem is to use the InternalsVisibleToAttribute on the assembly containing the anonymous type. However, this is code smell - just like any other use of InternalsVisibleToAttribute or internal itself.

A better way would be to make sure you don't actually pass anonymous types over assembly boundaries - after all, they shouldn't even be used outside of the method they originated from; the fact that they are is basically an implementation detail of .NET - they didn't have another way to do the same thing. This could change in future versions, making the InternalsVisibleToAttribute solution doubly unreliable.

The way your code is using dynamic suggests that your team has flawed assumptions about how dynamic works and how it's supposed to be used. Note how the actual runtime type of List<dynamic> is actually List<object>. The same goes for arguments of type dynamic (which are again just object, albeit marked with DynamicAttribute). And in fact, that really is what dynamic is - it's a way to handle runtime dynamic dispatch - it's not a property of the type or anything, it's just the way you actually invoke whatever you're trying to invoke. For C#, dynamic allows you to skip most of the compiler checks when working with those dynamic types, and it generates some code to handle the dispatch for you automatically, but all of that only happens inside the method where you actually use the dynamic keyword - if you used List<object>, the end result would be exactly the same.

In your code, there's no reason not to use simple static types. Dynamic typing doesn't really give you any benefits, apart from the effort to code the types themselves. If your co-workers don't like that, well, they should present a better solution - the problem is quite obvious, and it's something you need to deal with.

Much worse, it explicitly hides all context, all the type information. That's not something you want in an API, internal or not! If you want to hide the concrete types being used, why not - but you should still expose an interface instead. I suspect this is the reason why anonymous types can't implement interfaces - it would encourage you to go entirely the wrong way.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • Thanks for David Arno as well for pointing to Jon Skeet's [article](http://csharpindepth.com/Articles/Chapter14/DynamicGotchas.aspx) that mentions this. My first check on this was poorly done. – Veverke May 12 '15 at 13:30
  • Luaan, please add that the solution is to add `InternalsVisibleTo("theCallingAssemblyName")` to the assembly where the anonymous type is defined/created, because you do not point out a solution :-) – Veverke May 12 '15 at 13:40
  • @Veverke Updated. And thanks :) I should really do some new posts, it's not like I don't have stuff to write about... :D – Luaan May 12 '15 at 14:04
  • 1
    @Veverke That's almost certainly the wrong solution. There's a reason these types are internal. They're not designed to be used outside of the scope of the method they're created in. You're spending a *ton* of effort trying to work around this. Just create new named types for your queries instead of abusing anonymous types like this. – Servy May 12 '15 at 14:06
  • @Servy Yeah, one big reason is the name alone - it's guaranteed to be unique in the assembly it originates from, but obviously it will clash with anonymous types in other assemblies. It would be very nice to get F#-like records in C# to handle cases like this. I don't mind creating my little immutable types manually, but... :D – Luaan May 12 '15 at 14:07
  • 1
    @Servy: discussing about the architecture/structure of the program I am working on is out of question. I personally think there is no need in the first place to be creating an anonymous type in my scenario, in the first place. But seems my team disagrees. In any case, for the sake of the problem in question - marking the assembly as visible to another **is a solution to the problem**, so that should be accepted as an answer, regardless of whether it is advisable/recommendable or not. This is a distinct issue. – Veverke May 12 '15 at 14:13
  • 1
    @Veverke It's an actively harmful answer, so no, it should *not* be the accepted answer. The accepted answer should be a solution to the problem that's *actually helpful*. Encouraging people to use bad solutions is to be actively malicious. – Servy May 12 '15 at 14:16
  • @Servy I think I'm pretty explicit about it being a bad idea, and I am proposing the better solution as well, so I don't see what you're talking about. – Luaan May 12 '15 at 14:27
  • @Servy: you are not being realistic nor practical, in my opinion. People end up in stackoverflow after googling for a problem they are facing in the hopes someone else did face it too in before. If they find a solution, they might upvote the answer and the question. And that's it. The question-and-answering scheme is, by definition, a punctual resolution to a specific problem. Would my question be "what is the best way implement this this and that? I am willing to use anonymous types here, any objections ?" Than I would agree with you. – Veverke May 12 '15 at 14:29
  • @Servy: SO is not about solving all software development problems. It's a humble question-and-answer forum that enjoys much success - _precisely_ because of the described above. – Veverke May 12 '15 at 14:30
  • 1
    @Veverke Yes, you're quite right that SO is here so that future visitors can find solutions to their problem. When the answers are encouraging *actively harmful solutions to those problems that they absolutely shouldn't be using* then that's *a bad thing*. When they're proposing quality solutions that will not only solve the problem, but result in a *helpful* solution that doesn't just cause way more problems than it solves, then the site is doing its job. – Servy May 12 '15 at 14:31
  • Luaan, in any case, if you are of the opinion of @Servy, and when posting this answer did not intend to provide a solution to the problem (which would imply "you should then not be posting it as an answer"), then I will not mark it as an answer indeed, unless you add the "MakeVisibleTo" attribute thing. – Veverke May 12 '15 at 14:33
  • @Veverke The fact that you don't like the *actual* solution to the problem, and prefer to use a hacky workaround, doesn't make the actual solution not the superior answer. – Servy May 12 '15 at 14:35
  • @Servy: entering this dicussion will take us a few minutes... but since when do free stuff provide the *best* / *ideal* solution to your problem ? And if you want to be picky, then you must not relate to this _solution_ as unhelpful. I am pretty sure that you accept that anyone else facing the same problem and ending up here would upvote this, if it gets them rid of the exception. And no, those users did not end up here because they are interested in changing the architecture of the project they are working with, because they are not being paid for that. – Veverke May 12 '15 at 14:36
  • @Servy: I would re-evaluate the hole thing and do not let such harmful scenario happen. – Veverke May 12 '15 at 14:37
  • @Servy: totally agree with your last comment. Just disagree with it not being at all a solution to the problem. – Veverke May 12 '15 at 14:39
  • @Veverke The fact that people would upvote a harmful solution because it appears, at first glance, to solve the problem, even though it's going to cause way more subtle problems that they aren't noticing isn't a justification for it being a good answer. Bad answers that appear to be good answers when they aren't are *far* more harmful than bad answers that clearly look bad. – Servy May 12 '15 at 14:39
  • @Veverke I didn't say it wasn't a solution, I said it was an actively harmful solution. – Servy May 12 '15 at 14:40
  • @Servy Okay, I made it a lot more explicit. Better? – Luaan May 12 '15 at 14:46
  • I do not believe my team has assumptions to start with, regarding *dynamic*, just like the rest of the *majority* of the users who end up using it. Unfortunately, we all (me included) tend to pick the shorter paths rather than the long (and probably correct) ones. – Veverke May 12 '15 at 14:48
  • I totally agree with your last 2 paragraphs. Unfortunately the thing is that it does not matter. – Veverke May 12 '15 at 14:54