Модификация анонимного типа в рантайме

198
07 августа 2018, 21:40

В хелпер asp.net я передаю переменную htmlAttributes вида:

new { @class = "btn btn-default", title = "classic view" };

При этом внутри хелпера мне иногда нужно добавить css класс active при выполнении некоторого условия:

new { @class = "btn btn-default active", title = "classic view" };

Как это сделать проще всего?

Сам я попробовал полезть через рефлексию

foreach (var i in htmlAttributes.GetType().GetProperties())
{
    Console.Write(i.GetValue(a));
}

, но вспомнил, что анонимные типы - только для чтения, они вообще без setter'ов создаются.

Можно ли создать копию, с немного другими значениями?

Answer 1

Тут все зависит от того что потребитель будет делать с этим объектом. Для передачи в хелперы Razor проще всего преобразовать объект в RouteValueDictionary и дальше использовать другую перегрузку:

var dict = new RouteValueDictionary(obj);
dict["class"] += " active";

Также можно "обмануть" потребителя передав ему объект реализующий ICustomTypeDescriptor - многие потребители, и Razor в их числе, используют именно ComponentModel вместо рефлексии для получения свойств объекта.

Если же объект предполагается передавать в интерпретируемые языки использующие DLR - то можно использовать ExpandoObject:

var dict = (IDictionary<string, object>)new ExpandoObject();
foreach (var pair in new RouteValueDictionary(obj))
    dict.Add(pair);
dynamic foo = dict;
foo.@class += " active";

Ну а если требуется точное совпадение типа - ничего не поделать, придется использовать вариант Андрея и надеяться что разработчики компилятора не поменяют особенности реализации...

Answer 2

Просто создадим новый объект через рефлексию, передавая массив значений в конструктор:

static object CreateModified(object obj, string propName, object newValue)
{
    var type = obj.GetType();
    var props = type.GetProperties();
    var values = props.Select(p => p.Name == propName ? newValue : p.GetValue(obj)).ToArray();
    return Activator.CreateInstance(type, values);
}

Решение довольно хрупкое, полагается на то, что в текущей реализации компилятора свойства в анонимном классе создаются в том же порядке, что и параметры его конструктора.

Используем:

var a = new { @class = "btn btn-default", title = "classic view" };
Console.WriteLine(a);
var b = CreateModified(a, "class", a.@class + " active");
Console.WriteLine(b);

Вроде работает, но делать я так бы не стал.

READ ALSO
Чтение файла частями C#

Чтение файла частями C#

Есть файл, пусть будет Scriptps1 powershell

180
Проверка на наличие в DataGridView

Проверка на наличие в DataGridView

Требуется пройти по DataGridView и найти там определенные данные, введенные с textboxКак это реализовать?

155