Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Mr.Delphist  
Дата: 26.08.16 11:09
Оценка:
Коллеги, прошу совета — у меня глаз замылился, похоже

Есть вот такой код, по сути копипаста с небольшими вариациями, но с тенденцией к разрастанию количества экземпляров.
public void ValidateFoo()
{
  var item = _factory.FirstOrDefault(x => x.UName == Foo.UniqueName);
  if (item == null)
  {
    _factory.Register<Foo>(new FactoryRecord
    {
      UName = Foo.UniqueName,
    });
  } else {
    // всякий Foo-специфичный код, можно вытащить в лямбду, Action и т.п.
  }
}

public void ValidateBar()
{
  var item = _factory.FirstOrDefault(x => x.UName == Bar.UniqueName);
  if (item == null)
  {
    _factory.Register<Bar>(new FactoryRecord
    {
      UName = Bar.UniqueName,
    });
  } else {
    // всякий Bar-специфичный код, можно вытащить в лямбду, Action и т.п.
  }
}


Хочется пресечь это на корню и написать Validate<T>, параметризуемый нужным типом.
Небольшая засада с самими классами: там есть атрибут, требуемый сторонней либой. Атрибут ссылается на константу из класса
[SomeAttribute(Foo.UniqueName)]
public class Foo
{
  public const string UniqueName = "This is Foo";
  // всякий Foo-специфичный набор полей
}

[SomeAttribute(Bar.UniqueName)]
public class Bar
{
  public const string UniqueName = "This is Bar";
  // всякий Bar-специфичный набор полей
}


Понятное дело, что атрибут хочет именно константу в роли своего параметра, иначе ой. Иначе и вопроса бы не было — накидал бы генерик-метод через интерфейс или что-то такое.

Как выкрутиться и написать Validate<T>?
Re: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: vorona  
Дата: 26.08.16 11:23
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

string uname = typeof(T).GetCustomAttribute<SomeAttribute>().UniqueName
Re: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Sinix  
Дата: 26.08.16 11:35
Оценка: 5 (1)
Здравствуйте, Mr.Delphist, Вы писали:

Как обычно, начинаем с классики

CHRIS SELLS
As a test of whether an API is "simple," I like to submit it to a test I call "client-first programming."

If you say what your library does and ask a developer to write a program against what he or she expects such a library to look like (without actually looking at your library), does the developer come up with something substantially similar to what you've produced?
Do this with a few developers. If the majority of them write similar things without matching what you've produced, they're right, you're wrong, and your library should be updated appropriately.

I find this technique so useful that I often design library APIs by writing the client code I wish I could write and then just implement the library to match that.

(c)Framework Design Guidelines

и пришем:
static readonly Validator<Bar> barValidator = Validator.For((Bar b) => Bar.UniqueName);
...
barValidator.Validate(bar);


Поскольку инстанс кэшируется в static-поле, то внутри factory-метода можно применять любые извращения, вплоть до кодогенерации поверх expression trees.

that's all. Если условие (Bar.UniqueName) всегда одинаковое будет, то:
1. Делаем интерфейс и реализуем его явно.
2. Вытаскиваем значение атрибута ч/з рефлексию и запоминаем в словаре. Что важно — любой из вариантов не затронет сам принцип => остальной код менять не придётся.

P.S. _factory.FirstOrDefault() я бы на словарь заменил, O(N) — не самая лучшая идея для hotpath code.
Re[2]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Mr.Delphist  
Дата: 26.08.16 12:55
Оценка:
Здравствуйте, vorona, Вы писали:

V>Здравствуйте, Mr.Delphist, Вы писали:


V>
V>string uname = typeof(T).GetCustomAttribute<SomeAttribute>().UniqueName
V>


Да, но плохо тем, что никакой проверки на T не привязать. Есть атрибут или нет — узнаем только в рантайме (в пятницу вечером в продакшне ).
Re[2]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Mr.Delphist  
Дата: 26.08.16 13:00
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, Mr.Delphist, Вы писали:


S>и пришем:

S>
S>static readonly Validator<Bar> barValidator = Validator.For((Bar b) => Bar.UniqueName); <- статик поле? а если two-phase init и валидация может начаться лишь во второй фазе?
S>...
S>barValidator.Validate(bar); <-- инстанс? а без него никак?
S>
Re[3]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Sinix  
Дата: 26.08.16 13:10
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

MD>инстанс? а без него никак?


Ну да. Инстанс, закэшированный в static-поле.
Но это если яправильно твой сценарий понял. Тут важен не пример, что я привёл, а то, что в цитате. Попробуй сам набросать пример использования твоего API, все нерабочие варианты быстро отпадут.
Re[4]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Mr.Delphist  
Дата: 26.08.16 16:29
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Ну да. Инстанс, закэшированный в static-поле.


Не-не-не, у меня операции именно над типами нужны — sorry, из исходного поста это было неочевидно. Поэтому нигде и нету инстансов — т.е. получается либо static, либо const. Вот и думаю, как же генериками это дело обыграть, чтоб не копипастить.
Re[5]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Sinix  
Дата: 26.08.16 16:47
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

MD>Не-не-не, у меня операции именно над типами нужны — sorry, из исходного поста это было неочевидно.

Ну так инстанс хелпера Validator<T>, в котором будет метод Validate(T foo), а не типа, с которым вы работаете. Типы ж не static-классы?
Re[6]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Mr.Delphist  
Дата: 27.08.16 19:54
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, Mr.Delphist, Вы писали:


MD>>Не-не-не, у меня операции именно над типами нужны — sorry, из исходного поста это было неочевидно.

S>Ну так инстанс хелпера Validator<T>, в котором будет метод Validate(T foo), а не типа, с которым вы работаете. Типы ж не static-классы?

Вот у меня в этом и затык — как написать генерик-валидатор.

public void Validate<T>() where T : что дать тут в качестве констрейна?
{
var item = _factory.FirstOrDefault(x => x.UName == T.UniqueName); вот тут как объяснить компилятору, что надо взять константу или статик-поле (или что-то ещё) класса T
if (item == null)
{
_factory.Register<T>(new FactoryRecord
{
UName = T.UniqueName, аналогично предыдущему
});
} else {
... далее уже не так принципиально ...
}
}
Re[7]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Sinix  
Дата: 27.08.16 20:25
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

MD>Вот у меня в этом и затык — как написать генерик-валидатор.


Ну тут сложно посоветовать _правильный_ вариант, потому что у тебя в коде смешиваются зависимости. Тут и собственно валидация, и получение записи из фабрики, и регистрация, если не найдено. *(имена если что, неудачные. Тру-фабрика не предусматривает каких-то действий с собой после создания и не хранит инстансы объектов).

При таком подходе правильного кода не может быть в принципе. А вот если отделить мух от котлет и оформить код в стиле
static void Validate<T>(T arg, ...)
{
  var validator = GetOrAddValidatorFor(arg, ...);
  validator.Validate(...);
}

то расправиться с каждым из кусков по отдельности (получение валидатора, возможно с кэшированием + собственно валидация) проблем обычно не представляет.

Я ж говорю — напиши сначала реальный сценарий использования. Вот есть тип SomeClass и требуется прикрутить к нему валидацию. Как оно будет выглядеть?
Re: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: okon  
Дата: 27.08.16 22:07
Оценка: +1
MD>Понятное дело, что атрибут хочет именно константу в роли своего параметра, иначе ой. Иначе и вопроса бы не было — накидал бы генерик-метод через интерфейс или что-то такое.
MD>Как выкрутиться и написать Validate<T>?

А с какой целью заведен аттрибут в который передается UniqueName ?
Почему например бы не изобретать свой UniqueName а взять полное имя типа .net, оно же уникально.


А решение можно сделать например, если SomeAttribute это свой аттрибут и есть его обработчик

    public interface IUniqueName
    {
        string UniqueName { get; }
    }

    public abstract class Base
    {
        public bool Validate<T>(T obj) where T : IUniqueName
        {
            return false;
        }
    }


    [SomeAttribute]
    public class Foo : Base, IUniqueName
    {
        public string UniqueName
        {
            get { return "Foo"; }
        }
    }

    [SomeAttribute]
    public class Bar : Base, IUniqueName
    {
        public string UniqueName {
            get { return "Bar"; }
        }
    }
    public class SomeAttribute : Attribute
    {
        public SomeAttribute()
        {
            
        }

        public string GetUniqueObjectName(object o)
        {
            if (o is IUniqueName)
                return (o as IUniqueName).UniqueName;

            throw new ArgumentException("Only IUnqiueName interface supported.");
        }
    }


    public class Logic
    {


        public void Process(object o)
        {
            foreach (SomeAttribute a in o.GetType().GetCustomAttributes(typeof(SomeAttribute), true))
            {
               var name = a.GetUniqueObjectName(o);
               Console.WriteLine(name);
            }

        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            var logic = new Logic();
            logic.Process(new Bar());
            logic.Process(new Foo());
            Console.ReadKey();
        }
    }
”Жить стало лучше... но противнее. Люди которые ставят точку после слова лучше становятся сторонниками Путина, наши же сторонники делают акцент на слове противнее ( ложь, воровство, лицемерие, вражда )." (с) Борис Немцов
Re[2]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Sinix  
Дата: 28.08.16 07:05
Оценка:
Здравствуйте, okon, Вы писали:

O>А решение можно сделать например, если SomeAttribute это свой аттрибут и есть его обработчик

вот всё ок, но вот так делать низзя.
    public abstract class Base
    {
        public bool Validate<T>(T obj) where T : IUniqueName
        {
            return false;
        }
    }


Самый очевидный довод: вот такой вот код легальным будет: foo.Validate(bar);. Посложнее: валидация очень часто зависит от контекста. Привязывать валидацию к самому объекту == тащить логику для всех частных случаев в сам объект.
Re[2]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Mr.Delphist  
Дата: 29.08.16 10:07
Оценка:
Здравствуйте, okon, Вы писали:

O>А с какой целью заведен аттрибут в который передается UniqueName ?

O>Почему например бы не изобретать свой UniqueName а взять полное имя типа .net, оно же уникально.

3rd-party беспощаден, обычное для enterprise дело.

Небольшая засада с самими классами: там есть атрибут, требуемый сторонней либой.


Использовать в роли связки имя типа не хотелось бы — иначе если переименуют класс, то всё скомпилируется, но в рантайме развалится.
Re[7]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Jack128  
Дата: 29.08.16 10:24
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:


MD>Вот у меня в этом и затык — как написать генерик-валидатор.


MD>public void Validate<T>() where T : что дать тут в качестве констрейна?

MD>{
MD> var item = _factory.FirstOrDefault(x => x.UName == T.UniqueName); вот тут как объяснить компилятору, что надо взять константу или статик-поле (или что-то ещё) класса T
MD> if (item == null)
MD> {
MD> _factory.Register<T>(new FactoryRecord
MD> {
MD> UName = T.UniqueName, аналогично предыдущему
MD> });
MD> } else {
MD> ... далее уже не так принципиально ...
MD> }
MD>}

Я не понял, тебе это чтоли нужно :
var uniqueName = (SomeAttribute)(typeof(T).GetCustomAttibutes(typeof(SomeAttribute), true)[0]).UniqueName;
var item = factory.FirstOrDefault(x => x.UName == T.UniqueName);
if (item == null)
{
    _factory.Register<T>(new FactoryRecord
    {
      UName = uniqueName,// аналогично предыдущему
    });
}
Re[8]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Mr.Delphist  
Дата: 29.08.16 14:40
Оценка:
Здравствуйте, Jack128, Вы писали:

J>Я не понял, тебе это чтоли нужно :

J>
J>var uniqueName = (SomeAttribute)(typeof(T).GetCustomAttibutes(typeof(SomeAttribute), true)[0]).UniqueName;
J>var item = factory.FirstOrDefault(x => x.UName == T.UniqueName);
J>if (item == null)
J>{
J>    _factory.Register<T>(new FactoryRecord
J>    {
J>      UName = uniqueName,// аналогично предыдущему
J>    });
J>}
J>


Да, почти. Хотелось бы лишь первую строку так сделать, чтобы попытка передать класс T без нужного атрибута вызывала ошибку компиляции вместо рантайм-ошибки. Т.е. чтобы просматривался каким-то образом контракт, облегчающий жизнь "тем, кто будет после нас".
Re[9]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
От: Sinix  
Дата: 29.08.16 14:50
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

MD>Хотелось бы лишь первую строку так сделать, чтобы попытка передать класс T без нужного атрибута вызывала ошибку компиляции вместо рантайм-ошибки. Т.е. чтобы просматривался каким-то образом контракт, облегчающий жизнь "тем, кто будет после нас".


С этого и надо было начинать Тот же код, но в профиль.
static readonly AttributeKey<Foo> _typedKey = Key.For((Foo foo) => Foo.SomeStaticField);
// or
static readonly AttributeKey<Foo> _typedKey = Key.For<Foo>(() => Foo.SomeStaticField);

...
var item = factory.TryGet(_typedKey);
...


Разумеется, в лямбде можно опечататься и указать другой тип, но тут вариантов особых нет. Или не проверяем вообще, или выносим в интерфейс, или способ выше, или пишем roslyn analyser. Собственно всё.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.