Возможно ли одним методом получить Generic-поле разных типов
От: LWhisper  
Дата: 22.01.16 17:42
Оценка:
Есть Generic-тип, например List<T>.
Есть несколько объектов: List<Int32>, List<Object>, List<String>
Возможно ли сгенерировать общий метод на основе определения List<>, который бы получал значение вложенного Generic-поля, например T[] _items;

То есть что-нибудь наподобие:
delegate Array GetItems(Object obj);

GetItems getItems = EmitGetItems(typeof(List<>));
Array arr1 = getItems(new List<Int32>{1, 2, 5});
Array arr2 = getItems(new List<Object>(null, "String", new List<Int32>());
Array arr3 = getItems(new List<String>("Str1", "Str2");


Reflection.GetValue, к примеру, такого не умеет, если ты пытаешься вытащить поле, дескриптор которого помечен, как IsGenericParameter. Но, может быть, это можно сделать при помощи IL'а?
Отредактировано 22.01.2016 17:42 LWhisper . Предыдущая версия .
.net c# il expression reflection hack
Re: Возможно ли одним методом получить Generic-поле разных типов
От: hardcase Пират http://nemerle.org
Дата: 25.01.16 10:14
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Возможно ли сгенерировать общий метод на основе определения List<>, который бы получал значение вложенного Generic-поля, например T[] _items;


Какая задача вообще решается?
Идея использования private членов типов выглядит не самым лучшим образом.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[2]: Возможно ли одним методом получить Generic-поле разны
От: LWhisper  
Дата: 25.01.16 10:57
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Какая задача вообще решается?

H>Идея использования private членов типов выглядит не самым лучшим образом.

Сериализация и десериализация. Хотелось вытянуть при сериализации внутренний массив, а при десериализации — подсунуть на место. На самом деле, хочется воспользоваться конструктором с указанием Capacity и просто добавлять элементы по мере десериализации. Проблема заключается в том, что работаю исключительно с упакованными в Object данными и пытаюсь не использовать Reflection, а если это необходимо, то компилировать их через Expression или Emit в лямбды вида Action/Func<Object, Object> для вызова без DynamicInvoke.
Но в случае с List<T> и Dictionary<TKey,TValue> кэшировать такие методы слишком накладно. Вот и пытаюсь найти способ работать с Generic полями, свойствами, используя конкретные типы только для каста и распаковки. С Nullable-типами всё было просто так как Expression.Convert умеет преобразовывать примитивные типы даже в нулябельные энамы. А вот с боле сложными Generic-типами непонятно что делать.

---UPD:
Ох, ваш покорный слуга — дятел. -_-
List<T> ведь реализует IList, а Dictionary<TKey, TValue> — IDictionary.
Проблема решена. Но вопрос всё-таки не считаю решённым, так как было бы интересно — возможно это или нет? С List и Dictionary повезло. Не факт, что повезёт с другими. (Например, у Stack<T> отсутствует Pust(Object))
Отредактировано 25.01.2016 12:22 LWhisper . Предыдущая версия . Еще …
Отредактировано 25.01.2016 11:02 LWhisper . Предыдущая версия .
Re[3]: Возможно ли одним методом получить Generic-поле разны
От: hardcase Пират http://nemerle.org
Дата: 25.01.16 12:56
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Сериализация и десериализация. Хотелось вытянуть при сериализации внутренний массив, а при десериализации — подсунуть на место.


Воплощенные generic типы — это отдельные типы, поэтому следует иметь столько же копий кода, сколько имеется различных подстановок типов-парамтров, иначе привет рефлексия и постоянные тайпкасты.
Для часто используемых и хорошо известных структур данных, таких как массивы, List<T>, Dictionary<K, V> и другие известные коллекции следует иметь обобщенную библиотечную реализацию — это и будет оптимизация.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[4]: Возможно ли одним методом получить Generic-поле разны
От: LWhisper  
Дата: 01.02.16 09:20
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Воплощенные generic типы — это отдельные типы, поэтому следует иметь столько же копий кода, сколько имеется различных подстановок типов-парамтров, иначе привет рефлексия и постоянные тайпкасты.

Это известно. От этого и пытаюсь избавиться.

H>Для часто используемых и хорошо известных структур данных, таких как массивы, List<T>, Dictionary<K, V> и другие известные коллекции следует иметь обобщенную библиотечную реализацию — это и будет оптимизация.

Вот с библиотечной реализацией как раз проблемы. У меня есть произвольный тип, помеченный как сериализуемый. Я разбираю его на отдельные поля и свойства, после чего приступаю к десериализации из бинарного потока.
Я могу оперировать понятиями Type и Object. Я не могу описать в коде обобщённые методы, которые принимали бы в качестве параметров что-либо, отличное от Object. В противном случае мне придётся вызывать эти методы при помощи DynamicInvoke, что существенно замедлит производительность.
Вот и приходится выкручиваться — создаю динамический метод который принимает и отдаёт упакованные в Object экземпляры, а внутри уже распаковывает их при помощи Expression Unbox/Cast и Emit Unbox/Unbox_any/Castclass.

Если не затруднит, помоги выработать наиболее правильную стратегию на примере Stack<T>. Буду очень благодарен.

То есть:
Имеется тип с полем Stack<Int32> MyStack = new Stack<Int32>();
Есть двоичный поток: 4 (размер), 1, 2, 3, 4 (значения)
Нужно десериализовать стек.
Точка входа: Stack<Int32> deserializedStack = (Stack<Int32>)Deserialize(typeof(Stack<Int32>), inputStream);

Известные данные:
Type stackType = typeof(Stack<Int32>); // Тип известен только в рантайме
Type stackGenericType = stackType.GetGenericTypeDefinition();
Type stackParameterType = stackType.GetGenericArguments()[0];
Re[5]: Возможно ли одним методом получить Generic-поле разны
От: Sinix  
Дата: 01.02.16 09:39
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Если не затруднит, помоги выработать наиболее правильную стратегию на примере Stack<T>. Буду очень благодарен.

В крупную клетку, для сериализации:
1. Реализовать поддержку IDataContractSurrogate или ISerializationSurrogate.
2. Реализовать суррогаты для всех нетривиальных типов (как минимум — исключения, ISet, IDictionary, IList надо покрыть). В большинстве случаев будет достаточно написать что-то типа такого
class StackSurrogate<T>
{
  public object Serialize(object stack)
  {
    var stackTyped = (Stack<T>)stack;
    ...
  }
}
и кэшировать экземпляры.

3. Для кода, для которого нет суррогатов придётся генерить код для быстрого получения значений полей, чтобы не утыкаться в рефлексию. Самый универсальный способ — комбинация Reflection.Emit + expression trees. Сгенеренные хелперы тоже кэшировать.


Для десериализации — то же самое, только генерировать код для конструкторов, чтобы не утыкаться в Activator.CreateInstance<T>().
И начните с тестов + реализации на любой из готовых библиотек. Кучу времени сэкономите.

P.S. При сериализации словарей/сетов не забыть про comparer. Очень популярная ошибка.
Re[6]: Возможно ли одним методом получить Generic-поле разны
От: LWhisper  
Дата: 03.02.16 23:28
Оценка:
Здравствуйте, Sinix, Вы писали:

S>В крупную клетку, для сериализации:

S>1. Реализовать поддержку IDataContractSurrogate или ISerializationSurrogate.
S>2. Реализовать суррогаты для всех нетривиальных типов (как минимум — исключения, ISet, IDictionary, IList надо покрыть). В большинстве случаев будет достаточно написать что-то типа такого
S>
S>class StackSurrogate<T>
S>{
S>  public object Serialize(object stack)
S>  {
S>    var stackTyped = (Stack<T>)stack;
S>    ...
S>  }
S>}
S>
и кэшировать экземпляры.


Хм. Замечательно, а каким образом мне конструировать StackSurrogate<T>?
Вопрос то заключается именно в этом. Если бы я мог оперировать Generic-типами, сериализовать тот же Stack<T> не составило бы труда. Но я же не могу сделать метод вида
SerializeStack(Object obj, Stream output)
{
Stack<> stack = (Stack<>)obj;
}

Я должен указать тип. А тип я не знаю. А если конструировать его при помощи рефлекшенов и активаторов, встаёт вопрос — как всё это кэшировать. И если у меня будет 1000 различных Stack<T>, это значит что в кэше мне придётся держать 1000 различных реализаций Сурогатов. А если у Generic-типа больше одного параметра? Пришли к тому с чего начали — как написать методы Push (Object) и Object Pop, подцепив их к базовому определнию Stack<> с кастом внутри к нужному типу? А в остальном вся инфраструктура уже описана. Не хватает только удобного механизма сериализации нативных Generic'ов. ( Должен же быть способ как-нибудь наILить...

P.S. Спасибо за замечание про Comparer!
Re[7]: Возможно ли одним методом получить Generic-поле разны
От: Sinix  
Дата: 04.02.16 06:12
Оценка: +1
Здравствуйте, LWhisper, Вы писали:


LW>Я должен указать тип. А тип я не знаю. А если конструировать его при помощи рефлекшенов и активаторов, встаёт вопрос — как всё это кэшировать.

Это абсолютно стандартная проблема абсолютно для всех библиотек, которые должны быстро работать с заранее неизвестными типами.
И решается тоже стандартно.

Храним словарь <Тип, инстанс с логикой для типа>. Инстанс, разумеется, должен быть stateless, словарь — concurrent. Создать один раз за всё время жизни программы инстанс ч/з Activator.CreateInstance() или через emit не проблема как бы.
Дальше всё просто, public api принимает/отдает object, каст к реальному типу — внутри методов.

LW>И если у меня будет 1000 различных Stack<T>, это значит что в кэше мне придётся держать 1000 различных реализаций Сурогатов.

Ну и что? ~100 КБ на всё про всё (это с запасом, в реальности меньше будет, т.к. jit переиспользует код для генериков с ссылочными параметрами), как бы не являются проблемой.
Поиск по словарю — тоже.
Re[8]: Возможно ли одним методом получить Generic-поле разны
От: LWhisper  
Дата: 04.02.16 12:15
Оценка:
Здравствуйте, Sinix, Вы писали:

LW>>И если у меня будет 1000 различных Stack<T>, это значит что в кэше мне придётся держать 1000 различных реализаций Сурогатов.

S>Ну и что? ~100 КБ на всё про всё (это с запасом, в реальности меньше будет, т.к. jit переиспользует код для генериков с ссылочными параметрами), как бы не являются проблемой.
S>Поиск по словарю — тоже.

Понятно.
В общем, вопрос звучит так — есть ли альтернатива этому стандартному подходу?
Именно так я кэширую Emit'ие методы сериализации для не Generic-типов. Но делать это для Generic'ов не хочу. Вот и ищу способ при помощи какой-нибудь IL-инъекции описать обобщённый метод, работающий с Object'ами.
Не найду — создам словарь. Но очень, ОЧЕНЬ, ОЧЕНЬ не хочется этого делать. Вот прямо выворачивает от этой мысли. Меня от лишнего вызова .GetType коробит, а тут что-то совсем нелицеприятное получается. Перфекционист внутри меня негодует.
Отредактировано 04.02.2016 12:23 LWhisper . Предыдущая версия .
Re[9]: Возможно ли одним методом получить Generic-поле разны
От: Sinix  
Дата: 04.02.16 12:35
Оценка: +1
Здравствуйте, LWhisper, Вы писали:

LW>В общем, вопрос звучит так — есть ли альтернатива этому стандартному подходу?

Да, конечно.
* Можно просто получать все значения ч/з нетипизированные интерфейсы, см на IEnumerable.
* Можно подсмотреть логику у готовых сериализаторов. Выберите наиболее подходящий вот тут и смотрите, как оно сделано у конкурентов.
* Можно кэшировать не сами инстансы, а делегаты, сгенеренные ч/з emit. (ну надо же что-то делать)(с)

LW>Не найду — создам словарь. Но очень, ОЧЕНЬ, ОЧЕНЬ не хочется этого делать. Вот прямо выворачивает от этой мысли. Меня от лишнего вызова .GetType коробит, а тут что-то совсем нелицеприятное получается. Перфекционист внутри меня негодует.

Ну тогда прямая дорога в кодогенерацию при компиляции. Хардкодим поддерживаемые типы, для каждого генерируем сериализацию, сохраняем как сборку. Куда ещё эффективней-то?

Если серьёзно, то надо иметь представление о стоимости того или иного действия. Нарабатывается только практикой, увы. Вызов GetType() и поиск по словарю стоит копейки, не туда копаете.
Re[10]: Возможно ли одним методом получить Generic-поле разны
От: LWhisper  
Дата: 05.02.16 11:47
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Да, конечно.

S>* Можно просто получать все значения ч/з нетипизированные интерфейсы, см на IEnumerable.
S>* Можно подсмотреть логику у готовых сериализаторов. Выберите наиболее подходящий вот тут и смотрите, как оно сделано у конкурентов.
S>* Можно кэшировать не сами инстансы, а делегаты, сгенеренные ч/з emit. (ну надо же что-то делать)(с)
Первый случай не подходит для десериализации. Stack<T> не наследуются от IStack.
У конкурентов посмотрю, спасибо.
Последнее: те же яйца, только в профиль.

S>Ну тогда прямая дорога в кодогенерацию при компиляции. Хардкодим поддерживаемые типы, для каждого генерируем сериализацию, сохраняем как сборку. Куда ещё эффективней-то?

Пардон, а чем это поможет? Если бы, я при помощи Mono.Cecil, добавлял в нативные сборки методы сериализации — это одно (но я же не самоубийца). А так передо мной встанет тот же самый вопрос — как вызвать нужный мне класс/метод из этой самой сборки, имея на руках упакованный в Object экземпляр при сериализации или Type при десериализации. Воткнусь в ту же самую необходимость констурировать Generic-классы/методы посредством рефлекшена и кэшировать их, чтобы не использовать рефлекшен в дальнейшем. О.о Написать что-нибудь вроде
Object Deserialize(Type type)
{
 if (type == typeof(Stack<int>)
return  DeserializeStack<int>();
 if (type == typeof(Stack<long>)
return  DeserializeStack<long>();
...
}

...я конечно могу, но это явно не то, чего мне хочется достичь.

S>Если серьёзно, то надо иметь представление о стоимости того или иного действия. Нарабатывается только практикой, увы. Вызов GetType() и поиск по словарю стоит копейки, не туда копаете.

А у меня уже идут микро-оптимизации. Все тяжеловестные операции я отловил профилировщиком. Теперь упираюсь исключительно в IO. Я готов смириться с неизбежностью. Только вместо Dictionary сделаю дерево, чтобы можно было кэшировать Generic'и с любым числом параметров, просто перечисляя их последовательно. Фактически, уже начал имплементить. Но мне хотелось бы быть уверенным, что это действительно единственный выход в данной ситуации и ничего улчше даже при помощи генерации грязнейшего недокументированного IL'а добиться нельзя.
Re[11]: Возможно ли одним методом получить Generic-поле разны
От: Sinix  
Дата: 05.02.16 12:20
Оценка:
Здравствуйте, LWhisper, Вы писали:

S>>Ну тогда прямая дорога в кодогенерацию при компиляции. Хардкодим поддерживаемые типы, для каждого генерируем сериализацию, сохраняем как сборку. Куда ещё эффективней-то?

LW>Пардон, а чем это поможет?

Ну смотрите: если не выходит ускорить код, даже зная конечный набор типов и имея возможность сгенерить произвольный код при компиляции, то на какую магию тут можно надеяться?
Не, серьёзно, как вы собираетесь сделать подбор кода сериализации под произвольный тип эффективнее словаря с его O(1)?

LW>А у меня уже идут микро-оптимизации. Все тяжеловестные операции я отловил профилировщиком. Теперь упираюсь исключительно в IO.

Неправильно меряете значит. Надо замерять с Stream, который тупо ничего не делает. Иначе ваши реально узкие места потеряются на фоне инфраструктуры.

Ну и в идеале, собирать лог на сценариях, которые длятся хотя бы несколько минут, причём в sampling, в tracing и в allocation tracing mode.
Разумеется, не каждый раз так извращаться, а в самом начале, чтобы быть уверенным, что вы правильно определили узкие места.
Re[12]: Возможно ли одним методом получить Generic-поле разны
От: LWhisper  
Дата: 05.02.16 14:47
Оценка:
S>Ну смотрите: если не выходит ускорить код, даже зная конечный набор типов и имея возможность сгенерить произвольный код при компиляции, то на какую магию тут можно надеяться?
S>Не, серьёзно, как вы собираетесь сделать подбор кода сериализации под произвольный тип эффективнее словаря с его O(1)?
Не знаю, поэтому и спрашиваю.

S>Неправильно меряете значит. Надо замерять с Stream, который тупо ничего не делает. Иначе ваши реально узкие места потеряются на фоне инфраструктуры.

В таком варианте тоже проверял. Пока самым узким местом являются вызовы Array.CreateInstance и Array.SetValue.

S>Ну и в идеале, собирать лог на сценариях, которые длятся хотя бы несколько минут, причём в sampling, в tracing и в allocation tracing mode.

S>Разумеется, не каждый раз так извращаться, а в самом начале, чтобы быть уверенным, что вы правильно определили узкие места.
Кхм. Ну, с такими сценариями возникают сложности, так как 100000 итераций десериализации гигантского (по меркам приложения) графа объектов из XML пролетают за 40 секунд (из бинарки за 4 секунды). Сериализация в XML в два раза быстрее, в бинарщину так же 4 секунды. А чтобы какой-то один объект сериализововался дольше минуты, да ещё без учёта IO. Это всю базу нужно вывозить...

Так что откровенно узких мест пока нет. Вот и занимаюсь улучшательствами — дженерики, цикличные графы с сохранением ссылок, сериализация объектов за интерфейсами и т.д.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.