List<dynamic> elements have fields but I cannot access them. Why?
Asked Answered
S

1

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.

Shade answered 12/5, 2015 at 10:16 Comment(18)
Does it result in an exception, or just blank fields? Also, posting your code would be useful.Homesick
@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.Shade
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]'Homesick
@ChrisMantle: added it to the question, thanks.Shade
Does it work if you continue past the exception?Homesick
@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).Shade
Have a read of csharpindepth.com/Articles/Chapter14/DynamicGotchas.aspx. Are the values in section.Fields dynamic/anonymous, or are they static types of varying types?Cleanthes
We would like to know how you have initialized the Fields list as DavidArno just saidTorse
@DavidArno: thanks for the link. Yes, the List<dynamic> is made of an anonymous type, something like new { Name = ... , Id = ... , etc }. As with regards to the article you mention and the anonymous types issue, everything is taking place in the same assembly, so I assume this should not be the problem.Shade
@Shade 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 shownDecidua
@Shade 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?Ferricyanide
I've tried using code similar to yours (anonymous types in List<dynamic> 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? :DZendejas
Also, your error message {"'object' does not contain a definition for 'Name'"} is weird. Normally, it would say sth along lines {"'<>f_AnonymousType0<int, int>' does not contain a definition for 'Name'"}Hagler
@Hagler Yeah, I was just about to add that. I tried a dozen ways and couldn't reproduce that error. This is really weird :)Zendejas
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.Zendejas
@Luaan: that's what the article David Arno above posted mentions, and I checked that - the anonymous type is created in the same assembly.Shade
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.Zendejas
@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.Shade
Z
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.

Zendejas answered 12/5, 2015 at 13:27 Comment(21)
Thanks for David Arno as well for pointing to Jon Skeet's article that mentions this. My first check on this was poorly done.Shade
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 :-)Shade
@Shade Updated. And thanks :) I should really do some new posts, it's not like I don't have stuff to write about... :DZendejas
@Shade 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.Marmawke
@Marmawke 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... :DZendejas
@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.Shade
@Shade 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.Marmawke
@Marmawke 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.Zendejas
@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.Shade
@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.Shade
@Shade 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.Marmawke
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.Shade
@Shade 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.Marmawke
@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.Shade
@Servy: I would re-evaluate the hole thing and do not let such harmful scenario happen.Shade
@Servy: totally agree with your last comment. Just disagree with it not being at all a solution to the problem.Shade
@Shade 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.Marmawke
@Shade I didn't say it wasn't a solution, I said it was an actively harmful solution.Marmawke
@Marmawke Okay, I made it a lot more explicit. Better?Zendejas
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.Shade
I totally agree with your last 2 paragraphs. Unfortunately the thing is that it does not matter.Shade

© 2022 - 2024 — McMap. All rights reserved.