Во что обернуть Id - класс или структуру?
От: LWhisper  
Дата: 22.06.17 17:28
Оценка:
В продукте используется множество разнообразных Id представленных в виде Guid или String.
Не редко разработчики допускают ошибки, подменяя одно понятие другим в процессе взаимодействия между компонентами.
Появилось коварное желание обернуть наиболее проблемные из них в управляемые обертки.
И встал вопрос — во что? Классы или структуры?

По началу, выбор был очевиден — структуры, благодаря минимальному оверхеду.
Но есть недостаток — структуры не наследуются. А это означает, что:
1) На каждый тип будет создано по меньшей мере 5 методов — операторы сравнения, а также Equals, GetHashCode и ToString, для комфортной работы, ещё 4, для реализации IFormattable, IComparable, IComparable<Guid>, IEquatable<Guid>
2) В одном случае идентификаторы могут наследоваться. Условно говоря, путь к файлу является также путём на файловой системе. Значит в каждом наследнике придётся реализовать implicit-операторы для конвертации в родителя.

После этого выбор в пользу структур перестал быть столь однозначным.
Предположим, что таких типизированных идентификаторов будет не больше 300, но не меньше 100, в рабочем процессе участвует не меньше 100, но не больше 40 000 000 экземпляров сущностей, у каждой из которых есть такой идентификатор. Какое решение подойдёт больше и почему?
.NET performance class struct optimization
Re: Во что обернуть Id - класс или структуру?
От: Sinix  
Дата: 22.06.17 17:50
Оценка: 7 (2)
Здравствуйте, LWhisper, Вы писали:

UPD pilgrim_ ниже предложил вариант с Id<TEntity>. Лучший вариант, как по мне.

LW>1) На каждый тип будет создано по меньшей мере 5 методов — операторы сравнения, а также Equals, GetHashCode и ToString, для комфортной работы, ещё 4, для реализации IFormattable, IComparable, IComparable<Guid>, IEquatable<Guid>


Решается шаблоном t4. IEquatable<Guid> лучше не делать, через comparer выставить. IComparable для id бесполезен: код, завязанный на последовательную генерацию id — потенциальная мина под разнесение системы на несколько инстансов.

Да, Equals() надо в любом случае или оверрайдить, или дополнять проверкой на строгое совпадение типов объектов.
UPD и при любом раскладе операторы сравнения надо явно объявлять для каждого типа. Смысл в if (appleId == orangeId)?


LW>2) В одном случае идентификаторы могут наследоваться. Условно говоря, путь к файлу является также путём на файловой системе. Значит в каждом наследнике придётся реализовать implicit-операторы для конвертации в родителя.


Я бы так не делал по возможности. Из опыта — приводит к переусложнению кода из-за использования одних и тех же хелперов для разных кусков системы и как следствие — смешиванию ответственностей. Ну и кроме того, немного странно сначала вводить строгую типизацию и сразу же искать способ её обойти

Для структур проблему можно решить через интерфейс + набор extension-методов для этого интерфейса с генерик-параметром this. JIT достаточно умён, чтобы сгенерить нормальный код.

LW>После этого выбор в пользу структур перестал быть столь однозначным.

Почему же? Расклад простой.
Или у вас есть жёсткие лимиты по производительности и вы их проверяете автоматом (тогда выбор в пользу структур вполне оправдан).
Или перфомансом никто толком не занимается. Из практики при таком раскладе аллокация нескольких сотен объектов на каждый запрос не будет самой большой проблемой.

P.S. При попытке соорудить быструю обработку 40M сущностей у вас будут проблемы при любом раскладе и придётся или писать отдельные DTO для конкретных сценариев, или системно бороться с избыточными аллокациями по всему коду, или забить и оставить как есть
Отредактировано 23.06.2017 4:57 Sinix . Предыдущая версия . Еще …
Отредактировано 22.06.2017 17:53 Sinix . Предыдущая версия .
Re[2]: Во что обернуть Id - класс или структуру?
От: Sharov Россия  
Дата: 22.06.17 18:18
Оценка:
Здравствуйте, Sinix, Вы писали:

S>P.S. При попытке соорудить быструю обработку 40M сущностей у вас будут проблемы при любом раскладе и придётся или писать отдельные DTO для конкретных сценариев, или системно бороться с избыточными аллокациями по всему коду, или забить и оставить как есть


При структуре проблем будет больше, т.к. сущности (40М) будут постоянно туда-сюда копироваться. Лишний оверхед, кмк.
Кодом людям нужно помогать!
Re[3]: Во что обернуть Id - класс или структуру?
От: Mystic Artifact  
Дата: 22.06.17 19:19
Оценка:
Здравствуйте, Sharov, Вы писали:

S>При структуре проблем будет больше, т.к. сущности (40М) будут постоянно туда-сюда копироваться. Лишний оверхед, кмк.

Речь ведь только о использовании структур, как враппер над ID, т.е. оборачиваются int/guid/string: и, афаик, в большинстве случаев вообще ничего не изменится ни с точки зрения лэйаута объектов, ни добавит расходов на копирование/передачу т.к. sizeof таких структур будет таким же как оборачиваемый тип.
Понятно, что если есть код на подобии .Tag (object) = AppleId — получим дополнительный боксинг, которого при сыром string не было.
Но в любом случае, имхо, возня со структурами того стоит. Я "на днях" в трёх разных идентификаторах запутался (перепутал), и до тех пор пока не сделал параллельных тестов — этого невозможно было обнаружить. Но структуры городить лень. Хочется opaque и transparent type aliases из коробки (текущий using это не то => пропихивать в каждый файл это неудобно).
Хотя для int-ов можно ещё enum использовать.
Re: Во что обернуть Id - класс или структуру?
От: Kolesiki  
Дата: 22.06.17 22:06
Оценка: :)))
Здравствуйте, LWhisper, Вы писали:

LW>В продукте используется множество разнообразных Id представленных в виде Guid или String.

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

А как "обёртки" решат проблему? Что и во что вы собираетесь заворачивать?

Кто-то умный советовал решать такое ИМЕНАМИ, чтобы сразу по месту был виден косяк:
idS — это строковый идентификатор
idQ — guid

При использовании они сразу будут бросаться в глаза.
Re: Во что обернуть Id - класс или структуру?
От: pilgrim_ Россия  
Дата: 22.06.17 23:44
Оценка: 96 (5) +1
Здравствуйте, LWhisper, Вы писали:

LW>И встал вопрос — во что? Классы или структуры?


LW>По началу, выбор был очевиден — структуры, благодаря минимальному оверхеду.


+ к структурам — практически нулевой оверхед (разве что боксинг, а храним значение ссылочного типа, и его (боксинг) канешн лучше избегать)

LW>Но есть недостаток — структуры не наследуются. А это означает, что:

LW>1) На каждый тип будет создано по меньшей мере 5 методов — операторы сравнения, а также Equals, GetHashCode и ToString, для комфортной работы, ещё 4, для реализации IFormattable, IComparable, IComparable<Guid>, IEquatable<Guid>

хз, зачем идентификаторам наследование — ведь тебе по сути нужен типизированный alias.
А дублирование кода можн орешить маркерами/тэгами типа, напр.:

https://dotnetfiddle.net/SNuA6M

public struct PersonTag {}
public struct CompanyTag {}

public struct EntityId<TIdentifier, TTag> : IComparable<EntityId<TIdentifier, TTag>>, IEquatable<EntityId<TIdentifier, TTag>>
    where TIdentifier : IComparable<TIdentifier>, IEquatable<TIdentifier>
{
    private TIdentifier _value;
    
    public EntityId(TIdentifier value)
    {
        _value = value;
    }
    
    public TIdentifier Value
    {
        get { return _value; }
    }
    
    public override string ToString()
    {
        return _value.ToString();
    }
        
    public override bool Equals(object obj)
    {
        if (obj is EntityId<TIdentifier, TTag>)
        {
            var other = (EntityId<TIdentifier, TTag>) obj;
            return Equals(other);
        }
        return false;
    }
        
    public override int GetHashCode()
    {
        return _value.GetHashCode();
    }
        
    public bool Equals(EntityId<TIdentifier, TTag> other)
    {
        return _value.Equals(other.Value);
    }
        
    public int CompareTo(EntityId<TIdentifier, TTag> other)
    {
        return _value.CompareTo(other.Value);
    }

    public static bool operator== (EntityId<TIdentifier, TTag> id1, EntityId<TIdentifier, TTag> id2)    
    {
        return id1.Equals(id2);
    }
        
    public static bool operator != (EntityId<TIdentifier, TTag> id1, EntityId<TIdentifier, TTag> id2)    
    {
        return !id1.Equals(id2);
    }
        
}

public static class EntityId
{
    
    public static EntityId<TIdentifier, TTag> MakeId<TIdentifier, TTag>(TIdentifier value)
        where TIdentifier : IComparable<TIdentifier>, IEquatable<TIdentifier>
    {
        return new EntityId<TIdentifier, TTag>(value);
    }
    
    public static EntityId<int, TTag> MakeId<TTag>(int value)
    {
        return new EntityId<int, TTag>(value);
    }

    public static EntityId<Guid, TTag> MakeId<TTag>(Guid value)
    {
        return new EntityId<Guid, TTag>(value);
    }

    public static EntityId<string, TTag> MakeId<TTag>(string value)
    {
        return new EntityId<string, TTag>(value);
    }
}


LW>2) В одном случае идентификаторы могут наследоваться. Условно говоря, путь к файлу является также путём на файловой системе. Значит в каждом наследнике придётся реализовать implicit-операторы для конвертации в родителя.


тут не понял

LW>После этого выбор в пользу структур перестал быть столь однозначным.

LW>Предположим, что таких типизированных идентификаторов будет не больше 300, но не меньше 100, в рабочем процессе участвует не меньше 100, но не больше 40 000 000 экземпляров сущностей, у каждой из которых есть такой идентификатор. Какое решение подойдёт больше и почему?

в описанном выше примере — нужно наструячить 100-300 тэгов нужных сущностей.

Возможный минус этого подхода — многословность типа идентификатора (а может и не минус — т.к. тип ID четко описывает себя — какой нижележащий тип + к какой сущности он применим), но т.к. глобальных typedef/alias типов в .net нет, то выбор небольшой — либо копипаста генерация, либо описанный подход. Зачем id-классы и наследование в твоем случае неясно.

upd: тэг канешн может быть и типом сущности, к которой относится id
Отредактировано 22.06.2017 23:57 pilgrim_ . Предыдущая версия .
Re[3]: Во что обернуть Id - класс или структуру?
От: Sinix  
Дата: 23.06.17 04:48
Оценка:
Здравствуйте, Sharov, Вы писали:

S>При структуре проблем будет больше, т.к. сущности (40М) будут постоянно туда-сюда копироваться. Лишний оверхед, кмк.

Неа. Для 16 байт гуида разница в стоимости вызова в микроскоп незаметна будет.
Re[2]: Во что обернуть Id - класс или структуру?
От: Sinix  
Дата: 23.06.17 04:55
Оценка: 15 (1)
Здравствуйте, pilgrim_, Вы писали:

_>хз, зачем идентификаторам наследование — ведь тебе по сути нужен типизированный alias.

_>А дублирование кода можн орешить маркерами/тэгами типа, напр.:
Отличный вариант Можно отказаться от TIdentifier и сделать Id<TEntity>, который попутно решит кучу проблем с вспоминанием "от чего этот id?". Блин, и ведь я на позапрошлом проекте что-то такое прикручивал, сам не вспомнил. +1 голос за
Re[3]: Во что обернуть Id - класс или структуру?
От: LWhisper  
Дата: 26.06.17 11:52
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Отличный вариант Можно отказаться от TIdentifier и сделать Id<TEntity>, который попутно решит кучу проблем с вспоминанием "от чего этот id?". Блин, и ведь я на позапрошлом проекте что-то такое прикручивал, сам не вспомнил. +1 голос за


Но при этом моментально воткнулся в такую вот не совсем очевидную проблему, создав предефайненый Id в том же типе.
https://stackoverflow.com/questions/39995260/self-referencing-generic-member-in-struct

Нужно отвыкать от публичных readonly-полей. ((
Re[2]: Во что обернуть Id - класс или структуру?
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 29.06.17 09:25
Оценка: +1
Здравствуйте, Kolesiki, Вы писали:
K>Кто-то умный советовал решать такое ИМЕНАМИ, чтобы сразу по месту был виден косяк:
K>idS — это строковый идентификатор
K>idQ — guid
Венгерка — это typechecker для бедных. Морально устарела лет эдак 20 назад.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.