Получить значение динамического свойства C# через строку

Я хотел бы получить доступ к значению dynamic Свойство C# со строкой:

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

Как я могу получить значение d.value2 ("random"), если у меня есть только "value2" как строка? В javascript я мог бы сделать d ["value2"] для доступа к значению ("random"), но я не уверен, как это сделать с помощью C# и отражения. Самое близкое, что я пришел, это:

d.GetType().GetProperty("value2")... но я не знаю, как получить реальную стоимость от этого.

Как всегда, спасибо за вашу помощь!

15 ответов

Решение

Как только у вас есть PropertyInfo (от GetProperty), вам нужно позвонить GetValue и передать экземпляр, из которого вы хотите получить значение. В твоем случае:

d.GetType().GetProperty("value2").GetValue(d, null);
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);
}

Добавить ссылку на Microsoft.CSharp. Работает также для динамических типов и частных свойств и полей.

Изменить: Хотя этот подход работает, есть почти в 20 раз более быстрый метод из сборки Microsoft.VisualBasic.dll:

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

Динамит - это открытый исходный код .net std библиотека, что давайте назовем это как dynamic ключевое слово, но с использованием строки для имени свойства, а не компилятора, который делает это за вас, и в конечном итоге он становится равным отражению по скорости (что не так быстро, как при использовании ключевого слова dynamic, но это связано с дополнительными издержками кэширование динамически, где компилятор статически кэшируется).

Dynamic.InvokeGet(d,"value2");

Самый простой способ получения как setter и getter для собственности, которая работает для любого типа, включая dynamic а также ExpandoObject это использовать FastMember который также является самым быстрым методом (он использует Emit).

Вы можете получить TypeAccessor на основе данного типа или ObjectAccessor основанный на экземпляре данного типа.

Пример:

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");

В большинстве случаев, когда вы запрашиваете динамический объект, вы получаете ExpandoObject (не в приведенном выше примере с анонимным, но статически типизированным вопросом, но вы упоминаете JavaScript, и мой выбранный JSON-анализатор JsonFx, например, генерирует ExpandoObjects).

Если ваша динамика на самом деле является ExpandoObject, вы можете избежать отражения, приведя ее к IDictionary, как описано на http://msdn.microsoft.com/en-gb/library/system.dynamic.expandoobject.aspx.

Как только вы приведете к IDictionary, у вас есть доступ к полезным методам, таким как.Item и.ContainsKey.

GetProperty/GetValue не работает для данных Json, он всегда генерирует нулевое исключение, однако вы можете попробовать этот подход:

Сериализуйте ваш объект с помощью JsonConvert:

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

Затем получите к нему доступ, приведя его обратно к строке:

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

Это может работать напрямую, применяя Convert.ToString(request)["DynamicFieldName"], однако я не проверял.

Чтобы получить свойства из динамического документа, когда .GetType() возвращается null, попробуй это:

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

d.GetType().GetProperty("значение2")

возвращает объект PropertyInfo.

Так тогда делай

propertyInfo.GetValue(d)

Вот как я получил значение свойства динамического объекта:

    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)
        {

        }


    }

Некоторые из решений не работали с объектом типа значения, который я получил из строки json, возможно, потому, что в моем коде не было конкретного типа, похожего на объект, который я получил бы из строки json, так как я пошел об этом было

      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();

Это сработало для меня

В.Net core 3.1 вы можете попробовать вот так

d?.value2 , d?.value3

Как и в принятом ответе, вы также можете попробовать GetField вместо того GetProperty.

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

В зависимости от того, насколько реально Type был реализован, это может работать, когда GetProperty() не работает, и может быть даже быстрее.

Если у вас есть динамическая переменная, например, DapperRow, вы можете сначала создать ExpandoObject, а затем преобразовать Expando в IDictionary<string, object>. С этого момента возможно получение значения через имя свойства.

Вспомогательный метод 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;
    }

Пример использования, я выделил жирным шрифтом приведение, которое дает нам доступ в примере, я отметил важные биты буквами **:

        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.
                }
            }
        }

В моем примере я ищу значение свойства внутри динамического объекта DapperRow и сначала преобразую строку Dapper в ExpandoObject и помещаю ее в пакет свойств словаря, как показано и упомянуто в других ответах здесь.

Мой пример кода - это метод InsertMany для расширения Dapper, над которым я работаю, я хотел захватить несколько идентификаторов здесь после пакетной вставки.

Внутри хранимой процедуры:

      INSERT INTO [dbo].[Account] ([Title]....)
OUTPUT INSERTED.Id
VALUES( @Title.....)

Код С#:

      var id = await _dbConnection.ExecuteScalarAsync("CreateAccount", param, 
            null, null, CommandType.StoredProcedure);
account.Id = (int)id;

Вот окно отладки со значением, мы получаем его как объект, поэтому преобразуем его в int.

ИспользоватьdynamicсNewtonsoft.Json.JsonConvert.DeserializeObject:

      // Get JSON string of object
var obj = new { value1 = "some", value2 = "random", value3 = "value" };
var jsonString = JsonConvert.SerializeObject(obj);

// Use dynamic with JsonConvert.DeserializeObject
dynamic d = JsonConvert.DeserializeObject(jsonString);

// output = "some"
Console.WriteLine(d["value1"]);

Образец: https://dotnetfiddle.net/XGBLU1

Другие вопросы по тегам