ChatGPT解决这个技术问题 Extra ChatGPT

Get value of c# dynamic property via string

I'd like to access the value of a dynamic c# property with a string:

dynamic d = new { value1 = "some", value2 = "random", value3 = "value" };

How can I get the value of d.value2 ("random") if I only have "value2" as a string? In javascript, I could do d["value2"] to access the value ("random"), but I'm not sure how to do this with c# and reflection. The closest I've come is this:

d.GetType().GetProperty("value2") ... but I don't know how to get the actual value from that.

As always, thanks for your help!

Note that this is not the intended purpose of "dynamic" and that this scenario does not work any better with "dynamic" than it does with "object". "dynamic" makes it possible to access properties when the name of the property is known at compile time but the type is not. Since you know neither the name nor the type at compile time, dynamic is not going to help you.
@EricLippert I know this question is old but just to make a comment in case someone see it in the future. In some cases you can't choose whether to use dynamic or object (for instance when using the JSON parser) and you still might want to get the properties from a string (from a config file for instance) so this use is not that unusual as one might initially think.

A
Adam Robinson

Once you have your PropertyInfo (from GetProperty), you need to call GetValue and pass in the instance that you want to get the value from. In your case:

d.GetType().GetProperty("value2").GetValue(d, null);

I'm getting a 'd.GetType().GetProperty("value2").GetValue(d)' threw an exception of type 'System.Reflection.TargetInvocationException' dynamic {System.Reflection.TargetInvocationException} in the watch window with that..?
Think GetValue needs an additional parameter - e.g. d.GetType().GetProperty("value2").GetValue(d, null)
Will this work on a true dynamic ExpandoObject rather than an anonymous type? Since new {} creates a real anonymous type with defined properties, calling GetType/GetProperty makes sense, but what about ExpandoObject, which if you call GetType, you'll get a type that has the properties of ExpandoObject, but not necessarily its dynamic properties.
-1. This only work with simple .NET objects that were casted to dynamic. It will not work with any custom dynamic object like Expando or ViewBag used ASP.NET MVC
this is what works with Expando Object: (((IDictionary)x))["value1"]
I
IS4
public static object GetProperty(object target, string name)
{
    var site = System.Runtime.CompilerServices.CallSite<Func<System.Runtime.CompilerServices.CallSite, object, object>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.GetMember(0, name, target.GetType(), new[]{Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(0,null)}));
    return site.Target(site, target);
}

Add reference to Microsoft.CSharp. Works also for dynamic types and private properties and fields.

Edit: While this approach works, there is almost 20× faster method from the Microsoft.VisualBasic.dll assembly:

public static object GetProperty(object target, string name)
{
    return Microsoft.VisualBasic.CompilerServices.Versioned.CallByName(target, name, CallType.Get);
}

Just wanted to mention that the VisualBasic version is not equivalent to your original 'GetProperty' version (the GetProperty actually invokes the dynamic GetMember, which works even on Python objects in IronPython).
➕1 this worked for private properties when both FastMember and HyperDescriptor would not
@IllidanS4 when you compared the CallSite code vs CallByName code did you compare the two while caching the CallSite instance? I would suspect the cost of your first method is almost purely the activation of the Binder and CallSite, not the invocation of Target()
I wonder if you can check with this if a property exists before trying to get its value?? -- So far this is the only thing that got close to what I need (since I'm working with COM objects, which happen to be dynamic by themselves)
@Daniel For some objects, you can call IDispatch::GetTypeInfo and ITypeInfo::GetFuncDesc. Although it might not work for some types.
j
jbtule

Dynamitey is an open source .net std library, that let's you call it like the dynamic keyword, but using the a string for the property name rather than the compiler doing it for you, and it ends up being equal to reflection speedwise (which is not nearly as fast as using the dynamic keyword, but this is due to the extra overhead of caching dynamically, where the compiler caches statically).

Dynamic.InvokeGet(d,"value2");

M
MaYaN

The easiest method for obtaining both a setter and a getter for a property which works for any type including dynamic and ExpandoObject is to use FastMember which also happens to be the fastest method around (it uses Emit).

You can either get a TypeAccessor based on a given type or an ObjectAccessor based of an instance of a given type.

Example:

var staticData = new Test { Id = 1, Name = "France" };
var objAccessor = ObjectAccessor.Create(staticData);
objAccessor["Id"].Should().Be(1);
objAccessor["Name"].Should().Be("France");

var anonymous = new { Id = 2, Name = "Hilton" };
objAccessor = ObjectAccessor.Create(anonymous);
objAccessor["Id"].Should().Be(2);
objAccessor["Name"].Should().Be("Hilton");

dynamic expando = new ExpandoObject();
expando.Id = 3;
expando.Name = "Monica";
objAccessor = ObjectAccessor.Create(expando);
objAccessor["Id"].Should().Be(3);
objAccessor["Name"].Should().Be("Monica");

var typeAccessor = TypeAccessor.Create(staticData.GetType());
typeAccessor[staticData, "Id"].Should().Be(1);
typeAccessor[staticData, "Name"].Should().Be("France");

typeAccessor = TypeAccessor.Create(anonymous.GetType());
typeAccessor[anonymous, "Id"].Should().Be(2);
typeAccessor[anonymous, "Name"].Should().Be("Hilton");

typeAccessor = TypeAccessor.Create(expando.GetType());
((int)typeAccessor[expando, "Id"]).Should().Be(3);
((string)typeAccessor[expando, "Name"]).Should().Be("Monica");

F
Francis Norton

Much of the time when you ask for a dynamic object, you get an ExpandoObject (not in the question's anonymous-but-statically-typed example above, but you mention JavaScript and my chosen JSON parser JsonFx, for one, generates ExpandoObjects).

If your dynamic is in fact an ExpandoObject, you can avoid reflection by casting it to IDictionary, as described at http://msdn.microsoft.com/en-gb/library/system.dynamic.expandoobject.aspx.

Once you've cast to IDictionary, you have access to useful methods like .Item and .ContainsKey


Unfortunately, having to cast to IDictionary and using TryGetValue for example, results in a plain old object being returned. You cannot take advantage of implicit operators at that point, since they are only considered at compile time. For example, if I had an Int64Proxy class with implicit conversion to Int64?, then Int64? i = data.value; //data is ExpandoObject would automatically lookup and call the implicit operator. On the other hand, if I had to use IDictionary to test whether "value" field exists, I'd get an object back that will not cast without error to Int64?.
A
Anderson

The GetProperty/GetValue does not work for Json data, it always generate a null exception, however, you may try this approach:

Serialize your object using JsonConvert:

var z = Newtonsoft.Json.JsonConvert.DeserializeObject(Convert.ToString(request));

Then access it directly casting it back to string:

var pn = (string)z["DynamicFieldName"];

It may work straight applying the Convert.ToString(request)["DynamicFieldName"], however I haven't tested.


This method generates the error: error CS0021: Cannot apply indexing with [] to an expression of type 'object'. Use new JavaScriptSerializer().Deserialize<object>(json); to get to the "properties" in the way you suggested
This is worked like a charm! 20-Nov-2020 Thanks dude
J
James Gaunt

d.GetType().GetProperty("value2")

returns a PropertyInfo object.

So then do

propertyInfo.GetValue(d)

thanks, this was the correct answer, but as mentioned above, the GetValue(d) needs to be GetValue(d,null)
M
Marcelo Lima Braga

This is the way i ve got the value of a property value of a dinamic:

    public dynamic Post(dynamic value)
    {            
        try
        {
            if (value != null)
            {
                var valorCampos = "";

                foreach (Newtonsoft.Json.Linq.JProperty item in value)
                {
                    if (item.Name == "valorCampo")//property name
                        valorCampos = item.Value.ToString();
                }                                           

            }
        }
        catch (Exception ex)
        {

        }


    }

T
Trevor Reid

To get properties from dynamic doc when .GetType() returns null, try this:

var keyValuePairs = ((System.Collections.Generic.IDictionary<string, object>)doc);
var val = keyValuePairs["propertyName"].ToObject<YourModel>;

v
vyeluri5

In .Net core 3.1 you can try like this

d?.value2 , d?.value3

k
kudzanayi

Some of the solutions were not working with a valuekind object that I obtained from a json string, maybe because I did not have a concrete type in my code that was similar to the object that I would obtain from the json string, so how I went about it was

JsonElement myObject = System.Text.Json.JsonSerializer.Deserialize<JsonElement>(jsonStringRepresentationOfMyObject);

/*In this case I know that there is a property with 
the name Code, otherwise use TryGetProperty. This will 
still return a JsonElement*/

JsonElement propertyCode = myObject.GetProperty("Code");
        
/*Now with the JsonElement that represents the property, 
you can use several methods to retrieve the actual value, 
in this case I know that the value in the property is a string, 
so I use the GetString method on the object. If I knew the value 
was a double, then I would use the GetDouble() method on the object*/

string code = propertyCode.GetString();

That worked for me


Best for me, thank you
E
Efreeto

Similar to the accepted answer, you can also try GetField instead of GetProperty.

d.GetType().GetField("value2").GetValue(d);

Depending on how the actual Type was implemented, this may work when GetProperty() doesn't and can even be faster.


FYI Difference between Property and Field in C# 3.0+: stackoverflow.com/a/653799/2680660
T
Tore Aurstad

In case you have a dynamic variable such as a DapperRow for example you can first build up an ExpandoObject, then cast the Expando into an IDictionary. From then on, getting a value via the name of a property is possible.

Helper method ToExpandoObject:

public static ExpandoObject ToExpandoObject(object value)
    {
        IDictionary<string, object> dapperRowProperties = value as IDictionary<string, object>;
        IDictionary<string, object> expando = new ExpandoObject();
        if (dapperRowProperties == null)
        {
            return expando as ExpandoObject;
        }
        foreach (KeyValuePair<string, object> property in dapperRowProperties)
        {
            if (!expando.ContainsKey(property.Key))
            {
                expando.Add(property.Key, property.Value);
            }
            else
            {
                //prefix the colliding key with a random guid suffixed 
                expando.Add(property.Key + Guid.NewGuid().ToString("N"), property.Value);
            } 
        }
        return expando as ExpandoObject;
    }

Sample usage, I have marked in bold the casting which gives us access in the example, I have marked the important bits with the ** letters:

  using (var transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
        {
            foreach (var dynamicParametersForItem in dynamicParametersForItems)
            {
                var idsAfterInsertion = (await connection.QueryAsync<object>(sql, dynamicParametersForItem)).ToList();
                if (idsAfterInsertion != null && idsAfterInsertion.Any())
                {
                    **var idAfterInsertionDict = (IDictionary<string, object>) ToExpandoObject(idsAfterInsertion.First());**
                    string firstColumnKey = columnKeys.Select(c => c.Key).First();
                    **object idAfterInsertionValue = idAfterInsertionDict[firstColumnKey];**
                    addedIds.Add(idAfterInsertionValue); //we do not support compound keys, only items with one key column. Perhaps later versions will return multiple ids per inserted row for compound keys, this must be tested.
                }
            }
        }

In my example, I look up a property value inside a dynamic object DapperRow and first convert the Dapper row into an ExpandoObject and cast it into a dictionary property bag as shown and mentioned in other answers here.

My sample code is the InsertMany method for Dapper extension I am working on, I wanted to grab hold of the multiple ids here after the batch insert.