Здравствуйте, darklight, Вы писали:
D>Здравствуйте. У меня есть нтерсеный вопрос.
D>Как бы реализовать коллекцию, которая смогла бы хранить значения разных типов, являющихся типами значений, в т.ч. и примитивными, без упаковки типов.
Если задача реально требует алгебраических типов, то можно попробовать взять соответствующий язык:
type Foo =
| IntValue of int
| DoubleValue of float
| DecimalValue of decimal
let xs = [ IntValue 1; DoubleValue 1.23; DecimalValue 3.45M ]
List.iteri (printfn "%d: %A") xs
Или глянуть рефлектором в какие конструкции на C# превращается этот код
Здравствуйте, Sinix, Вы писали:
L>>Можно. Что-то типа. S>Это вариант с кучей внутренних коллекций.
Это не суть важно. Главное, что можно.
S>Стоимость поиска вложенной коллекции убъёт всю выгоду от отсутствия боксинга.
Не зная кол-во типов, об этом сложно судить.
S>Очень сомневаюсь, в существовании контейнера для любых структур, что будет быстрее List<object>.
Не хотелось бы повторяться, но все-таки обсуждение подхода с боксингом — своего рода офтопик в данной теме. Топикстартер это дал явно понять. Разве нет?
Здравствуйте, Lloyd, Вы писали: L>Ну не обязательно же так топорно делать. Можно, например сделать, чтобы колеекция возвращала какой-нить IValueAccessor<T>, который будет генериться в рантайме.
Нда?
Core2Duo E8400, 4Gb RAM, Win7 x64.
Build target x86; RELEASE; запуск без дебаггера (по Ctrl-F5).
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace ConsoleApplication1
{
interface IValueHolder
{
}
class ValueHolder<T> : IValueHolder
{
public T Value { get; private set; }
public ValueHolder(T value)
{
Value = value;
}
}
class IntValueHolder : IValueHolder
{
public int Value { get; private set; }
public IntValueHolder(int value)
{
Value = value;
}
}
class Program
{
const int count = 10 * 1000 * 1000;
static void Main(string[] args)
{
Run();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Run();
Console.Write("Done...");
Console.ReadKey();
}
private static void Run()
{
Console.WriteLine("--------");
Measure("List<int>", () =>
{
var list = new List<int>(count);
for (int i = 0; i < count; i++)
{
list.Add(i);
}
});
Measure("List<object>", () =>
{
var list = new List<object>(count);
for (int i = 0; i < count; i++)
{
list.Add(i);
}
});
Measure("ArrayList", () =>
{
var list = new ArrayList(count);
for (int i = 0; i < count; i++)
{
list.Add(i);
}
});
Measure("ValueHolder<int>", () =>
{
var list = new List<ValueHolder<int>>(count);
for (int i = 0; i < count; i++)
{
list.Add(new ValueHolder<int>(i));
}
});
Measure("IntValueHolder", () =>
{
var list = new List<IntValueHolder>(count);
for (int i = 0; i < count; i++)
{
list.Add(new IntValueHolder(i));
}
});
Measure("(IValueHolder)ValueHolder<int>", () =>
{
var list = new List<IValueHolder>(count);
for (int i = 0; i < count; i++)
{
list.Add(new ValueHolder<int>(i));
}
});
Measure("(IValueHolder)IntValueHolder", () =>
{
var list = new List<IValueHolder>(count);
for (int i = 0; i < count; i++)
{
list.Add(new IntValueHolder(i));
}
});
}
static void Measure(string message, Action callback)
{
long mem = GC.GetTotalMemory(true);
Stopwatch sw = Stopwatch.StartNew();
callback();
double elapsed = sw.ElapsedMilliseconds;
long mem2 = GC.GetTotalMemory(false);
Console.WriteLine(
"{0,31}:{1,10:#,##0.0} ms; mem: {2,10:#,##0.0} Kb",
message,
elapsed,
(mem2 - mem) / 1024.0);
}
}
}
Здравствуйте, Sinix, Вы писали:
S>Если используем List<IValueAccessor> — эффекта практически незаметно (для малого количества объектов List<object>/ArrayList раза в полтора быстрее, но эта разница вполне может быть погрешностью измерения).
Ну вообще-то уже по самой формулировке вопроса List<object>/ArrayList рассматривать нельзя. Поэтому говорить о его скорости нет никакого смысла.
Здравствуйте, Константин Л., Вы писали:
КЛ>Что-то мне подсказывает, что f#/nemerle используют для алгебраических типов что-то подобное.
Что-то не право.
Здравствуйте, darklight, Вы писали: D>И использовать внутр только для хранения данных во внутренней коллекции. Как я уже говорил — снаружи будет отдельный посредник через которого можно получать и добавлять данные типизированным (через generic) образом — так что у клиента будет шанс использовать нужный метод с нужной типизацией получаемого/возвращаемого значения — а остальное уже не важно.
Вы, главное, поясните — зачем это? Чем вас так не устраивает боксинг?
Вы что, всерьёз полагаете, что затраты на анбоксинг при извлечении из коллекции настолько велики, что хранение вот этого вашего "супервариантного" типа будет ими оправдано?
Теперь такой вопрос: откуда ваш клиентский код знает, какого типа будет значение? Если он всегда достаёт из Limit int, то почему не сделать вместо "коллекции" нормальный класс с именованным полем?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Константин Л., Вы писали:
КЛ>Здравствуйте, samius, Вы писали:
S>>Популярный механизм реализации АТД на ООП (общий предок + наследование) был предложен на первой странице Пельмешком.
КЛ>на f#?
Да, и еще он предложил глянуть в рефлектор.
O>>Во-вторых, перебирать все коллекции при добавлении/изменении значения по ключу (ну или еще каким-то способом организовать проверку общей уникальности ключа).
L>Гм. Такого требования вроде как не было.
"Добавление элементов с заменой значения, если таковой уже существует.
Получение значения по ключу.
Обновление значения по ключу."
То есть я так понимаю, в этом плане коллекция внешне должна быть похожа на IDictionary, при этом внутри делая "все красиво". Если реализация подразумевает использование нескольких приватных словарей (грубо говоря, отдельного на каждый тип), то должен же быть какой-то контроль за общей уникальностью?
Как бы реализовать коллекцию, которая смогла бы хранить значения разных типов, являющихся типами значений, в т.ч. и примитивными, без упаковки типов.
Использование generic-коллекций позволяет это хранить только типы значений одного типа, а вот как бы ъхранить значения разных типов значений без упаковки. Заранее типы значений для не известны, могут быть как структурами, так примитивными типами. И, хорошо было бы хранить также значение null (не прибегаю к упакованным nullable типам). Будем считать, что коллекция не требует упорядоченного индексирования.
Пока думаю, что нужно делать внутри коллекцию контейнеров-коллекций для каждого типа значений по отдельности и рапихивать добавляемые значения по этим коллекциям по ключу значения типа этих значений. А при перечислении (или посике) обходить эти коллекции а потоом вложенные коллекции-контнейрены.
Но вот, как бы ещё суметь вернуть значения разного типа из коллекции без боксинга?
Здравствуйте, darklight, Вы писали:
D>Как бы реализовать коллекцию, которая смогла бы хранить значения разных типов, являющихся типами значений, в т.ч. и примитивными, без упаковки типов.
Никак. Можно сделать List<IValueHolder>, но:
1) если ValueHolder<T> — структура, она будет упакована сама.
2) если ValueHolder<T> — класс, то всё равно произойдёт копирование хранимой структуры в поле ValueHolder, т.е. в кучу.
Производительность в любом случае будет меньше, чем в варианте с List<object>.
D>Пока думаю, что нужно делать внутри коллекцию контейнеров-коллекций для каждого типа значений по отдельности и рапихивать добавляемые значения по этим коллекциям по ключу значения типа этих значений. А при перечислении (или посике) обходить эти коллекции а потоом вложенные коллекции-контнейрены.
Будет в разы медленнее.
Наконец, по сравнению с остальной частью приложения это будут такие копейки...
Если не секрет, чего вы хотите добиться?
Здравствуйте, Sinix, Вы писали:
D>>Как бы реализовать коллекцию, которая смогла бы хранить значения разных типов, являющихся типами значений, в т.ч. и примитивными, без упаковки типов. S>Никак. Можно сделать List<IValueHolder>, но: S>1) если ValueHolder<T> — структура, она будет упакована сама. S>2) если ValueHolder<T> — класс, то всё равно произойдёт копирование хранимой структуры в поле ValueHolder, т.е. в кучу.
Ну не обязательно же так топорно делать. Можно, например сделать, чтобы колеекция возвращала какой-нить IValueAccessor<T>, который будет генериться в рантайме.
Здравствуйте, Пельмешко, Вы писали:
П>Если задача реально требует алгебраических типов, то можно попробовать взять соответствующий язык: П>Или глянуть рефлектором в какие конструкции на C# превращается этот код
По производительности будет то же самое, что и в моём посте выше. Проще не выделываться и взять List<object>.
Здравствуйте, Lloyd, Вы писали:
S>>Сразу же вопрос: что даст динамическая генерация IValueAccessor<T>. L>Возможность избавиться от боксинга.
Не соображу, как получить отсюда выигрыш.
Если используем List<IValueAccessor> — эффекта практически незаметно (для малого количества объектов List<object>/ArrayList раза в полтора быстрее, но эта разница вполне может быть погрешностью измерения).
Если наша коллекция внутри себя хранит несколько коллекций — по одной для каждого типа, как и предлагал топикстартер, то поиск нужной коллекции убьёт весь выигрыш.
Здравствуйте, Lloyd, Вы писали:
L>Ну вообще-то уже по самой формулировке вопроса List<object>/ArrayList рассматривать нельзя. Поэтому говорить о его скорости нет никакого смысла.
Согласен. Только как можно добиться производительности, сопоставимой с List<T> — ума не приложу.
Здравствуйте, Sinix, Вы писали:
L>>Ну вообще-то уже по самой формулировке вопроса List<object>/ArrayList рассматривать нельзя. Поэтому говорить о его скорости нет никакого смысла. S>Согласен. Только как можно добиться производительности, сопоставимой с List<T> — ума не приложу.
Например, коллекция может возвращать типизированный "курсор" — аналог IEnumerable, который, зная о внутреннем устойстве коллекции будет уметь оптимально работать с элементами указанного типа.
Но, пока автор не прояснил, какие опреации он хочет видеть в своей коллекции, предлагать что-либо рано.
Здравствуйте, Lloyd, Вы писали:
L>Например, коллекция может возвращать типизированный "курсор" — аналог IEnumerable, который, зная о внутреннем устойстве коллекции будет уметь оптимально работать с элементами указанного типа.
Для начала неплохо бы определиться с тем, как нам объекты в коллекцию засовывать так, чтобы вся эта возня вообще имела смысл.
Помимо варианта с несколькими коллекциями можно придумать страшно извращённый способ: завести массив байт и копировать в него структуры (например, ч/з unsafe или Marshal.Copy). Даже если этот кошмарик будет быстро работать, тут же возникает другая проблема — либо мы выравниваем значения (и кидаем исключение для больших структур), либо тратим время для нахождения смещения. Баш на баш.
L>Но, пока автор не прояснил, какие опреации он хочет видеть в своей коллекции, предлагать что-либо рано.
Да тут, по-моему, любой подход кроме использования List<object>(или обёртки вокруг него) бессмысленен.
Здравствуйте, Sinix, Вы писали:
L>>Например, коллекция может возвращать типизированный "курсор" — аналог IEnumerable, который, зная о внутреннем устойстве коллекции будет уметь оптимально работать с элементами указанного типа.
S>Для начала неплохо бы определиться с тем, как нам объекты в коллекцию засовывать так, чтобы вся эта возня вообще имела смысл.
Вот и я о том же. Пока он не написал, каким он видит api его коллекции, предлагать что-либо бессмысленно
Здравствуйте, Sinix, спасибо огромное за Ваш пример с замерами производиетльности. Я, честно говоря в шоке. Получает generic-версия List<T> всёравно приводит с боксингу при работе со структурами. Меня, крайне поразило сравнение List<int> и List<ValueHolder<int>> — разница огромна (List<ValueHolder<int>> по времени то же, что и List<object>). Хотя, не может ли это быть из-за затрат времени на создание самих структур.
Я, если честно, собирался использовать создание структур для "переупаковки" значений внутр контейнера и при работе с внешним "потребителем" (посредник между разными интерфейсами). Придётся, наверное отказаться от этого и завести две внутренних-коллекций контейнера — одна для самих значений Dictionary<string, T> — и хранить только значения одного типа, другая для хранения пометки, что значение по ключу заполнено (нужна такая потребность) — Dictionary<string, bool> или HashSet<string>. А для остальных случаев — использовать обычный Dictionary<string, object> (или просто Dictionary) и не обращать внимание на опирации боксинга (не так уж это и критично — вроде как не critical realtime процессы буду использовать эту коллекцию), а с приведением типов быть поосторожнее
Хотя, конечно, сравнивать с List<T> другие коллекции — это дело заранее проигрышное — ибо Lits<T> самая оптимизированная по производительности коллекция всего .NET фреймворка, на данный момент.
Здравствуйте, darklight, Вы писали:
D>Хотя, конечно, сравнивать с List<T> другие коллекции — это дело заранее проигрышное — ибо Lits<T> самая оптимизированная по производительности коллекция всего .NET фреймворка, на данный момент.
Здравствуйте, darklight, Вы писали: D>Я, если честно, собирался использовать создание структур для "переупаковки" значений внутр контейнера и при работе с внешним "потребителем" (посредник между разными интерфейсами).
Пардон, у меня время позднее, попробую вдумчиво ответить завтра. А то щас намекнут, что я опять не понял вопроса D>Хотя, конечно, сравнивать с List<T> другие коллекции — это дело заранее проигрышное — ибо Lits<T> самая оптимизированная по производительности коллекция всего .NET фреймворка, на данный момент.
Хохма не в том. ValueHolder'ы — это классы. Соответственно, расходы на их создание ничем не отличаются от боксинга int. Замените class IntValueHolder на struct
код
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace ConsoleApplication1
{
interface IValueHolder
{
}
class ValueHolder<T> : IValueHolder
{
public T Value { get; private set; }
public ValueHolder(T value)
{
Value = value;
}
}
struct IntValueHolder : IValueHolder
{
public int Value { get; set; }
}
class Program
{
const int count = 10 * 1000 * 1000;
static void Main(string[] args)
{
Run();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Run();
Console.Write("Done...");
Console.ReadKey();
}
private static void Run()
{
Console.WriteLine("--------");
Measure("List<int>", () =>
{
var list = new List<int>(count);
for (int i = 0; i < count; i++)
{
list.Add(i);
}
});
Measure("List<object>", () =>
{
var list = new List<object>(count);
for (int i = 0; i < count; i++)
{
list.Add(i);
}
});
Measure("ArrayList", () =>
{
var list = new ArrayList(count);
for (int i = 0; i < count; i++)
{
list.Add(i);
}
});
Measure("ValueHolder<int>", () =>
{
var list = new List<ValueHolder<int>>(count);
for (int i = 0; i < count; i++)
{
list.Add(new ValueHolder<int>(i));
}
});
Measure("IntValueHolder", () =>
{
var list = new List<IntValueHolder>(count);
for (int i = 0; i < count; i++)
{
list.Add(new IntValueHolder() { Value = i});
}
});
Measure("(IValueHolder)ValueHolder<int>", () =>
{
var list = new List<IValueHolder>(count);
for (int i = 0; i < count; i++)
{
list.Add(new ValueHolder<int>(i));
}
});
Measure("(IValueHolder)IntValueHolder", () =>
{
var list = new List<IValueHolder>(count);
for (int i = 0; i < count; i++)
{
list.Add(new IntValueHolder {Value = i });
}
});
}
static void Measure(string message, Action callback)
{
long mem = GC.GetTotalMemory(true);
Stopwatch sw = Stopwatch.StartNew();
callback();
double elapsed = sw.ElapsedMilliseconds;
long mem2 = GC.GetTotalMemory(false);
Console.WriteLine(
"{0,31}:{1,10:#,##0.0} ms; mem: {2,10:#,##0.0} Kb",
message,
elapsed,
(mem2 - mem) / 1024.0);
}
}
}
API моей коллекции:
Хранение значений — как ссылочных, так и типов-значения (в т.ч. примитивных), а так же специального значения: Undefined — когда оно не заполнено (и все null ссылки автоматически приравниваются-преобразуются в это значение при добавлении).
Доступ к значению по ключу (строковому, но будет версия и с произвольным).
Добавление элементов с заменой значения, если таковой уже существует.
Получение значения по ключу.
Обновление значения по ключу.
Перебор коллекции с возвращение связной пары в виде структуры (ключ, значение, флаг, что значение было заполнено).
Удаление и очистка — есть, но практически не будет востребовано, больше будет востребовано быстрое создание и заполнение "фиксированным" набором значений, а так же их последующее обновление. Поиск и получение значения — так же должен быть очень быстрым.
К коллекция будет использована как основа для других коллекций — разного назначения: как маленьких (по количеству элементов) — требующих разового заполнения и разового использования, так и больших — требующих быстрого поиска и добавления/обновления. Будет версия и для dinamic-использования с прямым доступом через динамические поля к значениям, будет версия static-использования с привязкой фиксированного интерфейса в качестве аксесора к значениям коллекции через свойства интерфейса.
А вообще-то здесь больше востребована реализация промежуточного слоя для отделения коллекции-контейнера от потребителя отдельным ограниченным-интерфейсом (с определёнными наименованиями членов), хранение незаполненных значений по ключу (одного вида — для удобства сравнения, независимо для ссылочных и типов-значений объектов), а так же размещать внутри коллекции разные версии коллекций-конйтенеров (например sequantial и parallel, local и remote, array и database-access).
В общем, я видимо "забью" на боксинг и буд использовать Dictionary<string, object> для контейнера значений коллекции (+ отдельная версия Dictionary<string, T> для работы со значениями одного типа — ибо таких будет много; а первый вариант так же не проиграет в производительности и при значениях разного ссылочный-типа, только с привидением типов нужно будет быть поаккуратнее).
Но, в общем-то, generic версия List<T> при работе со структурами меня очень разочаровала — вот тебе и generic-преимущества при построении коллекций с типами-значениями не примитивных типов).
Здравствуйте, Lloyd, Вы писали: L>Да ну? И что ж в ней такого оптимизированного?
Просто я читал про это в интернете и в какой-то книге Сам не углублялся пока ещё в этот вопрос
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, darklight, Вы писали:
D>>Я, если честно, собирался использовать создание структур для "переупаковки" значений внутр контейнера и при работе с внешним "потребителем" (посредник между разными интерфейсами). S>Пардон, у меня время позднее, попробую вдумчиво ответить завтра. А то щас намекнут, что я опять не понял вопроса
S>Хохма не в том. ValueHolder'ы — это классы. Соответственно, расходы на их создание ничем не отличаются от боксинга int. Замените class
Вот те на, что я зря полез разбираться на нетрезвую голову ща прояснилось, что я не заметил, что это классы — а нафига они классы — я же про стукнуты говорил — и так опять-таки дилема — почему бы не хранить каждое значение в разной коллекции-контейнере (и по типу реальному типу значения, полученного через typeof — определять нужный индекс внешней коллекции:
class myContainer
{
Dictionary<Type, System.Collections.IDictionary> OuterCollection;
public void Add<T>(string key, T value)
{
var valueType = typeof (T);
System.Collections.IDictionary newInnerCollection;
if (!OuterCollection.ContainsKey(valueType))
{
newInnerCollection = new Dictionary<string, T>();
OuterCollection.Add(valueType,newInnerCollection);
}
else
{
newInnerCollection = OuterCollection[valueType];
}
Dictionary<string, T> innerCollection = (Dictionary<string, T>)newInnerCollection;
innerCollection.Add(key, value);
}
public T GetValue<T>(string key, out bool contains)
{
var valueType = typeof (T);
if (!OuterCollection.ContainsKey(valueType))
{
contains = false;
return default(T);
}
System.Collections.IDictionary _InnerCollection = OuterCollection[valueType];
Dictionary<string, T> innerCollection = (Dictionary<string, T>)_InnerCollection;
contains = innerCollection.ContainsKey(key);
if (!contains)
return default(T);
var value = innerCollection[key];
return value;
}
public void SetValue<T>(string key, T value)
{
var valueType = typeof (T);
System.Collections.IDictionary newInnerCollection;
if (!OuterCollection.ContainsKey(valueType))
{
newInnerCollection = new Dictionary<string, T>();
OuterCollection.Add(valueType,newInnerCollection);
}
else
{
newInnerCollection = OuterCollection[valueType];
}
Dictionary<string, T> innerCollection = (Dictionary<string, T>)newInnerCollection;
bool contains = innerCollection.ContainsKey(key);
if (!contains)
innerCollection.Add(key, value);
else
innerCollection[key] = value;
}
}
Здравствуйте, darklight, Вы писали:
D>Здравствуйте. У меня есть нтерсеный вопрос.
D>Как бы реализовать коллекцию, которая смогла бы хранить значения разных типов, являющихся типами значений, в т.ч. и примитивными, без упаковки типов.
D>Использование generic-коллекций позволяет это хранить только типы значений одного типа, а вот как бы ъхранить значения разных типов значений без упаковки. Заранее типы значений для не известны, могут быть как структурами, так примитивными типами. И, хорошо было бы хранить также значение null (не прибегаю к упакованным nullable типам). Будем считать, что коллекция не требует упорядоченного индексирования.
D>Пока думаю, что нужно делать внутри коллекцию контейнеров-коллекций для каждого типа значений по отдельности и рапихивать добавляемые значения по этим коллекциям по ключу значения типа этих значений. А при перечислении (или посике) обходить эти коллекции а потоом вложенные коллекции-контнейрены.
D>Но вот, как бы ещё суметь вернуть значения разного типа из коллекции без боксинга?
Пробовал протестировать свой вариант коллекции в тесте от Sinix — о ужас — затраты даже выше чем с боксингом Lits<object> (но ниже чем Dictionary<int, object>, но у полной реализации (myContainer) уже выше)
Но — удивляет что даже такой класс
class myContainer2<K>
{
Dictionary<Type, Dictionary<K, int>> OuterCollection = new Dictionary<Type, Dictionary<K, int>>(1000);
public myContainer2(int capacity)
{
OuterCollection.Add(typeof(int), new Dictionary<K, int>(capacity));
}
public void Add(K key, int value)
{
var innerCollection = OuterCollection[typeof(int)];
innerCollection.Add(key, value);
}
}
Measure("Dictionary<int, object>", () =>
{
var list = new Dictionary<int, object>(count);
for (int i = 0; i < count; i++)
{
list.Add(i, i);
}
});
Measure("Dictionary<int, ValueHolder<int>>", () =>
{
var list = new Dictionary<int, ValueHolder<int>>(count);
for (int i = 0; i < count; i++)
{
list.Add(i, new ValueHolder<int>() { Value = i });
}
});
Measure("myContainer", () =>
{
var list = new myContainer<int>(count);
for (int i = 0; i < count; i++)
{
//list.Add<ValueHolder<int>>(i, new ValueHolder<int>() { Value = i });
list.Add(i, i);
}
});
Measure("myContainer2", () =>
{
var list = new myContainer2<int>(count);
for (int i = 0; i < count; i++)
{
//list.Add<ValueHolder<int>>(i, new ValueHolder<int>() { Value = i });
list.Add(i, i);
}
});
Прямо-таки я остался очень озадачен данным результатом. Видимо придётся оставить реализацию с боксингом на Dictionary<string, object> — как наиболее простую и даже более производительную чем иные решения, которые могу прийти мне в голову.
код полного myContainer в последней версии:
class myContainer<K>
{
Dictionary<Type, System.Collections.IDictionary> OuterCollection = new Dictionary<Type, System.Collections.IDictionary>(1000);
private int capacity;
public myContainer(int capacity)
{
this.capacity = capacity;
}
public void Add<T>(K key, T value)
{
var valueType = typeof(T);
System.Collections.IDictionary newInnerCollection;
if (!OuterCollection.ContainsKey(valueType))
{
newInnerCollection = new Dictionary<K, T>(capacity);
OuterCollection.Add(valueType, newInnerCollection);
}
else
{
newInnerCollection = OuterCollection[valueType];
}
Dictionary<K, T> innerCollection = (Dictionary<K, T>)newInnerCollection;
innerCollection.Add(key, value);
}
public T GetValue<T>(K key, out bool contains)
{
var valueType = typeof(T);
if (!OuterCollection.ContainsKey(valueType))
{
contains = false;
return default(T);
}
System.Collections.IDictionary _InnerCollection = OuterCollection[valueType];
Dictionary<K, T> innerCollection = (Dictionary<K, T>)_InnerCollection;
contains = innerCollection.ContainsKey(key);
if (!contains)
return default(T);
var value = innerCollection[key];
return value;
}
public void SetValue<T>(K key, T value)
{
var valueType = typeof(T);
System.Collections.IDictionary newInnerCollection;
if (!OuterCollection.ContainsKey(valueType))
{
newInnerCollection = new Dictionary<K, T>();
OuterCollection.Add(valueType, newInnerCollection);
}
else
{
newInnerCollection = OuterCollection[valueType];
}
Dictionary<K, T> innerCollection = (Dictionary<K, T>)newInnerCollection;
bool contains = innerCollection.ContainsKey(key);
if (!contains)
innerCollection.Add(key, value);
else
innerCollection[key] = value;
}
}
Здравствуйте, darklight, Вы писали:
D>Здравствуйте, gandjustas, Вы писали:
G>>А какова цель всего этого действа?
D>вроде уже написал — смотри выше!
Не, расскажи что будет делать клиентский код.
Ведь даже если добьешься каким-то чудом отсутствия боксинга в коллекции, то клиентский код будет использовать обычные generic_и или тот же боксинг value-типов.
Дополнительный уровень косвенности, который ты внесешь, все равно ниче не даст.
ЗЫ. Кстати для mutable и immutable коллекций используются разные структуры данных.
Вобщем-то коллекция будет многоцелевая и использоваться в приложениях по работе с базами данных в основном на пользовательском слое (передача настроек, управление интерфейсом, параметров в запросы и т.п.).
Но будет версия и для обработки больших объёмов данных — например, в виде кеша и поиск по нему.
Что ещё сказать — не знаю.
G>Ведь даже если добьешься каким-то чудом отсутствия боксинга в коллекции, то клиентский код будет использовать обычные generic_и или тот же боксинг value-типов. G>ЗЫ. Кстати для mutable и immutable коллекций используются разные структуры данных.
Не совсем понял, что тут и тут имелось ввиду.
Поидеи клиенкский код в основном будет либо передавать значения констант (прописанных в коде в месте примения) дибо ссылочные объекты, либо получать и передавать значения из типизированных источников.
Здравствуйте, Lloyd, Вы писали:
L>Можно. Что-то типа.
Это вариант с кучей внутренних коллекций. Стоимость поиска вложенной коллекции убъёт всю выгоду от отсутствия боксинга.
Очень сомневаюсь, в существовании контейнера для любых структур, что будет быстрее List<object>.
Здравствуйте, darklight, Вы писали:
G>>Ведь даже если добьешься каким-то чудом отсутствия боксинга в коллекции, то клиентский код будет использовать обычные generic_и или тот же боксинг value-типов. G>>ЗЫ. Кстати для mutable и immutable коллекций используются разные структуры данных. D>Не совсем понял, что тут и тут имелось ввиду. D>Поидеи клиенкский код в основном будет либо передавать значения констант (прописанных в коде в месте примения) дибо ссылочные объекты, либо получать и передавать значения из типизированных источников.
Ну тогда клиентский код будет пользоваться Generic_ами или боксингом в object, иначе неюзабельно получится. И получатся теже результаты тестов независимо от того что используется внутри коллекций.
G>Ну тогда клиентский код будет пользоваться Generic_ами или боксингом в object, иначе неюзабельно получится. И получатся теже результаты тестов независимо от того что используется внутри коллекций.
Вот, я и говорю, что, видимо придётся оставить две версии: одну — generic — для работы со значениями строго одного типа — и одну с боксингом в object — для работы с любыми ссылочными типами и остальных смешанных случаев — когда допустимы элементы разных типов значений или в смеси с ссылочными типами. Хотя можно ещё предусмотреть один вариант (правда неэффектвиный с точки зрения расхода памяти, но это не очень принципиально): когда буду использоваться встроенные структруы-контейнеры для хранения значений основных типов-значений как одноц елоем — как-то вот так:
struct ValueContainer<TMyValue1, TMyValue2, TMyValue3, TMyValue4>
{
public enum TypeOfValue {ValueObject, ValueInt, ValueDecimal, ValueBool, Value1, Value2, Value3, Value4}
public object ValueObject; //For all ref objectspublic int ValueInt;
public Decimal ValueDecimal;
public bool ValueBool;
//For user's struct valuetypepublic TMyValue1 Value1;
public TMyValue2 Value2;
public TMyValue3 Value3;
public TMyValue4 Value4;
public TypeOfValue TypeValue;
public T GetValue<T>()
{
var t = typeof(T);
if ((t==typeof(int)) && (TypeValue==TypeOfValue.ValueInt))
return (T)ValueInt;
else if ((t==typeof(decimal)) && (TypeValue==TypeOfValue.ValueDecimal))
return (T)ValueDecimal;
//etcelse if ((t!=typeof(ValueType)) && (TypeValue==TypeOfValue.ValueObject))
return (T)ValueObject;
else
throw InvaliOperationException();
}
//public void SetValue(int value)
//public void SetValue(decimal value)
//etc
//public void SetValue(object value)public void SetValue<T>(T value)
{
var t = typeof(T);
if (t==typeof(int))
{
ValueInt = (int)value;
TypeValue = TypeOfValue.ValueInt;
}
else if (t==typeof(decimal))
{
ValueDecimal = (decimal)value;
TypeValue = TypeOfValue.ValueDecimal;
}
//etcelse if (t==typeof(TValue4))
{
Value4 = (TValue4)value;
TypeValue = TypeOfValue.Value4;
}
else
{
ValueObject = (object)value;
TypeValue = TypeOfValue.ValueObject;
}
}
}
Ну — ещё сюда бы добавить автоматическое приведение типов от меньшего к большему (float, double to decimal, заменить int на long и приводить int to long), аналогично преобразовывать при получении значения назад с созданием ошибки при преобразовании реально большего числа к меньшему.
Типы TValue1...TValue4 — предназначены для пользовательских структур — думаю, 4-х вариаций будет достаточно
Да, конечно, такой контейнер далёк от оптимальности по выделению памяти под большие коллекции — зато не будет боксинга в большинстве случаев. А для постых случаев — будут коллекции по одному generic-типу и с использованием object-контейнера.
Можно так же реализовать версию без пользовательских типов TValue1...TValue4, а так же для экстремального случая только с пользовательскими типами, но уже, скажем с 16-ью: TValue1...TValue16 + object-поле (примитивные типы могут быть размещены в одном из TValueN в случае необходимости) — итого будет 5 версий контейнера.
Здравствуйте, darklight, Вы писали:
D>Да, конечно, такой контейнер далёк от оптимальности по выделению памяти под большие коллекции — зато не будет боксинга в большинстве случаев. А для постых случаев — будут коллекции по одному generic-типу и с использованием object-контейнера.
Боксинг все равно будет в клиентском коде, если он не знает с каким типом работает, то все равно будет получать object где-то на входе. Ведь вы не сможете везде использовать вашу структуру ValueContainer, как минимум FCL о ней не знает.
Вот поэтому я и говорю что нет смысла добиваться отсутствия боксинга всякими хаками, лучше использовать generic_и или нетипизированные коллекции.
Здравствуйте, gandjustas, Вы писали:
D>>Да, конечно, такой контейнер далёк от оптимальности по выделению памяти под большие коллекции — зато не будет боксинга в большинстве случаев. А для постых случаев — будут коллекции по одному generic-типу и с использованием object-контейнера. G>Боксинг все равно будет в клиентском коде, если он не знает с каким типом работает, то все равно будет получать object где-то на входе. Ведь вы не сможете везде использовать вашу структуру ValueContainer, как минимум FCL о ней не знает.
Я её хочу использовать только внутри коллекции и не выдавать доступа к ней пользователю (ну только через отдельные методы — если пользователь точно знает с чем работает, например для передачи значения между несколькими коллекциями, поддерживающими такой подход, а весь API приложения будет поддерживать такой подход при работе с коллекциями и одиночными значениями).
И использовать внутр только для хранения данных во внутренней коллекции. Как я уже говорил — снаружи будет отдельный посредник через которого можно получать и добавлять данные типизированным (через generic) образом — так что у клиента будет шанс использовать нужный метод с нужной типизацией получаемого/возвращаемого значения — а остальное уже не важно.
Здравствуйте, darklight, Вы писали:
D>API моей коллекции: D>Хранение значений — как ссылочных, так и типов-значения (в т.ч. примитивных), а так же специального значения: Undefined — когда оно не заполнено (и все null ссылки автоматически приравниваются-преобразуются в это значение при добавлении). D>Доступ к значению по ключу (строковому, но будет версия и с произвольным).
И какая сигнатура будет у этого метода?
public ? get(String key) — вот что вместо вопросика планируется?
Думаю, как ни крути, придется методу возвращать object — но тогда, как бы вы ни хранили структуры внутри, даже и без упаковки, вот тут-то и получится боксинг.
А если сделать типизированные методы типа getInt16, getIn32 и т.п. — произвольные структуры хранить не получится.
Что-то не выходит цветок-то каменный.
Re[3]: Типы значений и коллекции
От:
Аноним
Дата:
22.09.10 21:35
Оценка:
Здравствуйте, Sinix, Вы писали:
S>По производительности будет то же самое, что и в моём посте выше. Проще не выделываться и взять List<object>.
лучше List<ValueType>
Здравствуйте, Ocelot, Вы писали:
O>И какая сигнатура будет у этого метода?
O>public ? get(String key) — вот что вместо вопросика планируется?
public T get<T>(String key)?
O>Думаю, как ни крути, придется методу возвращать object — но тогда, как бы вы ни хранили структуры внутри, даже и без упаковки, вот тут-то и получится боксинг.
O>А если сделать типизированные методы типа getInt16, getIn32 и т.п. — произвольные структуры хранить не получится.
Почему не получится-то? Внутри generic-метода get ищем словарь нужного типа (IDictionary<string, T>) и из него вытягиваем значение. Где в этом сценарии боксинг будет?
Здравствуйте, Пельмешко, Вы писали:
D>>Как бы реализовать коллекцию, которая смогла бы хранить значения разных типов, являющихся типами значений, в т.ч. и примитивными, без упаковки типов.
П>Или глянуть рефлектором в какие конструкции на C# превращается этот код
Здравствуйте, samius, Вы писали:
S>Здравствуйте, Константин Л., Вы писали:
КЛ>>Что-то мне подсказывает, что f#/nemerle используют для алгебраических типов что-то подобное. S>Что-то не право.
nemerle точно, только там механизм немного другой, а идея та же
Здравствуйте, Константин Л., Вы писали:
КЛ>Здравствуйте, samius, Вы писали:
S>>Здравствуйте, Константин Л., Вы писали:
КЛ>>>Что-то мне подсказывает, что f#/nemerle используют для алгебраических типов что-то подобное. S>>Что-то не право.
КЛ>nemerle точно, только там механизм немного другой, а идея та же
В зависимости от точки зрения варианты можно трактовать как ограниченный набор sealed-классов, имеющий общего предка, как эдакий типобезопасный аналог конструкции union из языка C, или как умную «штуковину», именуемую алгебраическими типами данных.
Сильно сомневаюсь, что реализация в немерле существенно отличается от данной трактовке и близка к объединению всех полей под все конструкторы в одной сущности. Хотя, не очень ясно, что значит "механизм немного другой"...
Здравствуйте, samius, Вы писали:
S>Здравствуйте, Константин Л., Вы писали:
КЛ>>Здравствуйте, samius, Вы писали:
S>>>Здравствуйте, Константин Л., Вы писали:
КЛ>>>>Что-то мне подсказывает, что f#/nemerle используют для алгебраических типов что-то подобное. S>>>Что-то не право.
КЛ>>nemerle точно, только там механизм немного другой, а идея та же
S>http://www.rsdn.ru/article/nemerle/Amplifier.xml#E62AE
S>В зависимости от точки зрения варианты можно трактовать как ограниченный набор sealed-классов, имеющий общего предка, как эдакий типобезопасный аналог конструкции union из языка C, или как умную «штуковину», именуемую алгебраическими типами данных.
S>Сильно сомневаюсь, что реализация в немерле существенно отличается от данной трактовке и близка к объединению всех полей под все конструкторы в одной сущности. Хотя, не очень ясно, что значит "механизм немного другой"...
не отличается, рефлектор это подтверждает, а вот общий предок и наследование/один типа как у меня — это и есть механизм.
Здравствуйте, Константин Л., Вы писали:
КЛ>не отличается, рефлектор это подтверждает, а вот общий предок и наследование/один типа как у меня — это и есть механизм.
А я уж было испугался за Nemerle
Популярный механизм реализации АТД на ООП (общий предок + наследование) был предложен на первой странице Пельмешком.
Здравствуйте, samius, Вы писали:
S>Здравствуйте, Константин Л., Вы писали:
КЛ>>не отличается, рефлектор это подтверждает, а вот общий предок и наследование/один типа как у меня — это и есть механизм.
S>А я уж было испугался за Nemerle S>Популярный механизм реализации АТД на ООП (общий предок + наследование) был предложен на первой странице Пельмешком.
Здравствуйте, samius, Вы писали:
S>Здравствуйте, Константин Л., Вы писали:
КЛ>>не отличается, рефлектор это подтверждает, а вот общий предок и наследование/один типа как у меня — это и есть механизм.
S>А я уж было испугался за Nemerle S>Популярный механизм реализации АТД на ООП (общий предок + наследование) был предложен на первой странице Пельмешком.
более того, прозрачно и удобно АТД без языковой поддержки можно сделать только с пом. насл., но там проверка типа + апкаст.
на чем ты меня хочешь поймать? идея одна и та же, только в f#/n наследование, а у меня "композиция".
Здравствуйте, samius, Вы писали:
S>Здравствуйте, Константин Л., Вы писали:
КЛ>>Здравствуйте, samius, Вы писали:
S>>>Популярный механизм реализации АТД на ООП (общий предок + наследование) был предложен на первой странице Пельмешком.
КЛ>>на f#? S>Да, и еще он предложил глянуть в рефлектор.
ты тут самоутверждаешься? как заимплеменчены variant's в nemerle я знаю и без тебя и не первый год.
Здравствуйте, Константин Л., Вы писали:
КЛ>>>на f#? S>>Да, и еще он предложил глянуть в рефлектор.
КЛ>ты тут самоутверждаешься? как заимплеменчены variant's в nemerle я знаю и без тебя и не первый год.
Нет, не самоутверждаюсь. Хотел внести ясность по поводу "подобности" механизмов наследования и композиции. Если ты и вправду хочешь считать их подобными, не буду мешать.
Здравствуйте, samius, Вы писали:
S>Здравствуйте, Константин Л., Вы писали:
КЛ>>>>на f#? S>>>Да, и еще он предложил глянуть в рефлектор.
КЛ>>ты тут самоутверждаешься? как заимплеменчены variant's в nemerle я знаю и без тебя и не первый год.
S>Нет, не самоутверждаюсь. Хотел внести ясность по поводу "подобности" механизмов наследования и композиции. Если ты и вправду хочешь считать их подобными, не буду мешать.
с точки зрения достижения конечной цели — да. а лекций по ооп и тп мне не нужно, спасибо
Здравствуйте, Lloyd, Вы писали:
O>>Думаю, как ни крути, придется методу возвращать object — но тогда, как бы вы ни хранили структуры внутри, даже и без упаковки, вот тут-то и получится боксинг.
L>Почему не получится-то? Внутри generic-метода get ищем словарь нужного типа (IDictionary<string, T>) и из него вытягиваем значение. Где в этом сценарии боксинг будет?
Да, согласен, погорячился.
Но оверхед будет еще тот — во-первых, сперва найти/добавить нужную типизированную коллекцию.
Во-вторых, перебирать все коллекции при добавлении/изменении значения по ключу (ну или еще каким-то способом организовать проверку общей уникальности ключа).
Сомневаюсь, что будет быстрее нетипизированного варианта с боксингом.
Здравствуйте, Ocelot, Вы писали:
O>Но оверхед будет еще тот — во-первых, сперва найти/добавить нужную типизированную коллекцию.
И где тут "еще тот"? Достаточно организавать словарь словарей. Вроде не должно быть особого оверхеда.
O>Во-вторых, перебирать все коллекции при добавлении/изменении значения по ключу (ну или еще каким-то способом организовать проверку общей уникальности ключа).
Здравствуйте, Lloyd, Вы писали:
L>public T get<T>(String key)?
O>>Думаю, как ни крути, придется методу возвращать object — но тогда, как бы вы ни хранили структуры внутри, даже и без упаковки, вот тут-то и получится боксинг.
L>Почему не получится-то? Внутри generic-метода get ищем словарь нужного типа (IDictionary<string, T>) и из него вытягиваем значение. Где в этом сценарии боксинг будет?
Да, и кстати, это все будет работать только если T заранее известно.
Типа string val = collection.get("key") — вызовется get<string>.
А как быть, если как раз и не известно, какого типа значение хранится по этому ключу (если вообще хранится)?
Доступ к значению по ключу автор упоминает, а вот запрос, какой тип имеет значение (естественно, без непосредственного запроса самого значения) явно упущен.
Мне кажется, в самой концепции таки есть противоречие, мало совместимое с жизнью.
Здравствуйте, Ocelot, Вы писали:
O>Здравствуйте, Lloyd, Вы писали:
L>>public T get<T>(String key)?
O>>>Думаю, как ни крути, придется методу возвращать object — но тогда, как бы вы ни хранили структуры внутри, даже и без упаковки, вот тут-то и получится боксинг.
L>>Почему не получится-то? Внутри generic-метода get ищем словарь нужного типа (IDictionary<string, T>) и из него вытягиваем значение. Где в этом сценарии боксинг будет?
O>Да, и кстати, это все будет работать только если T заранее известно.
А вот пусть даже и известно, кстати.
MyClass mc = collection.get("myclass");
mc.Property = newValue;
MyStruct ms = collection.get("mystruct");
ms.Property = newValue;
Надеюсь, идея ясна?
Как-то не очень горю желанием нарваться на такую коллекцию.
Здравствуйте, Ocelot, Вы писали:
L>>Почему не получится-то? Внутри generic-метода get ищем словарь нужного типа (IDictionary<string, T>) и из него вытягиваем значение. Где в этом сценарии боксинг будет?
O>Да, и кстати, это все будет работать только если T заранее известно. O>Типа string val = collection.get("key") — вызовется get<string>. O>А как быть, если как раз и не известно, какого типа значение хранится по этому ключу (если вообще хранится)?
Такая операция недопустима. Это следует из того, что не должно быть боксинга.
Здравствуйте, Lloyd, Вы писали:
L>Такая операция недопустима. Это следует из того, что не должно быть боксинга.
Вооот! Хотя у автора такого требования нет.
Более того, исходя из требования использования специального значения Undefined и наличия операции "Удаление и очистка" (когда вместо имеющегося значения по ключу будет этот самый Undefined и положен) следует, что тип того, что лежит в коллекции по ключу, запросто может меняться. А раз тип заранее не известен, то непосредственно типизированный вызов обобщенного метода сделать не получится.
Это именно и есть противоречие в концепции, о котором я уже говорил.