Коллеги, прошу совета — у меня глаз замылился, похоже
Есть вот такой код, по сути копипаста с небольшими вариациями, но с тенденцией к разрастанию количества экземпляров.
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: Изменяемый конст? Виртуальный статик? Как выкрутиться?
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.
Поскольку инстанс кэшируется в static-поле, то внутри factory-метода можно применять любые извращения, вплоть до кодогенерации поверх expression trees.
that's all. Если условие (Bar.UniqueName) всегда одинаковое будет, то:
1. Делаем интерфейс и реализуем его явно.
2. Вытаскиваем значение атрибута ч/з рефлексию и запоминаем в словаре. Что важно — любой из вариантов не затронет сам принцип => остальной код менять не придётся.
P.S. _factory.FirstOrDefault() я бы на словарь заменил, O(N) — не самая лучшая идея для hotpath code.
Re[2]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
Здравствуйте, 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]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
Здравствуйте, Mr.Delphist, Вы писали:
MD>инстанс? а без него никак?
Ну да. Инстанс, закэшированный в static-поле.
Но это если яправильно твой сценарий понял. Тут важен не пример, что я привёл, а то, что в цитате. Попробуй сам набросать пример использования твоего API, все нерабочие варианты быстро отпадут.
Re[4]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
Здравствуйте, Sinix, Вы писали:
S>Ну да. Инстанс, закэшированный в static-поле.
Не-не-не, у меня операции именно над типами нужны — sorry, из исходного поста это было неочевидно. Поэтому нигде и нету инстансов — т.е. получается либо static, либо const. Вот и думаю, как же генериками это дело обыграть, чтоб не копипастить.
Re[5]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
Здравствуйте, Mr.Delphist, Вы писали:
MD>Не-не-не, у меня операции именно над типами нужны — sorry, из исходного поста это было неочевидно.
Ну так инстанс хелпера Validator<T>, в котором будет метод Validate(T foo), а не типа, с которым вы работаете. Типы ж не static-классы?
Re[6]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
Здравствуйте, 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]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
Здравствуйте, Mr.Delphist, Вы писали:
MD>Вот у меня в этом и затык — как написать генерик-валидатор.
Ну тут сложно посоветовать _правильный_ вариант, потому что у тебя в коде смешиваются зависимости. Тут и собственно валидация, и получение записи из фабрики, и регистрация, если не найдено. *(имена если что, неудачные. Тру-фабрика не предусматривает каких-то действий с собой после создания и не хранит инстансы объектов).
При таком подходе правильного кода не может быть в принципе. А вот если отделить мух от котлет и оформить код в стиле
то расправиться с каждым из кусков по отдельности (получение валидатора, возможно с кэшированием + собственно валидация) проблем обычно не представляет.
Я ж говорю — напиши сначала реальный сценарий использования. Вот есть тип SomeClass и требуется прикрутить к нему валидацию. Как оно будет выглядеть?
Re: Изменяемый конст? Виртуальный статик? Как выкрутиться?
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]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
Здравствуйте, okon, Вы писали:
O>А решение можно сделать например, если SomeAttribute это свой аттрибут и есть его обработчик
вот всё ок, но вот так делать низзя.
public abstract class Base
{
public bool Validate<T>(T obj) where T : IUniqueName
{
return false;
}
}
Самый очевидный довод: вот такой вот код легальным будет: foo.Validate(bar);. Посложнее: валидация очень часто зависит от контекста. Привязывать валидацию к самому объекту == тащить логику для всех частных случаев в сам объект.
Re[2]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
Здравствуйте, okon, Вы писали:
O>А с какой целью заведен аттрибут в который передается UniqueName ? O>Почему например бы не изобретать свой UniqueName а взять полное имя типа .net, оно же уникально.
3rd-party беспощаден, обычное для enterprise дело.
Небольшая засада с самими классами: там есть атрибут, требуемый сторонней либой.
Использовать в роли связки имя типа не хотелось бы — иначе если переименуют класс, то всё скомпилируется, но в рантайме развалится.
Re[7]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
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]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
Да, почти. Хотелось бы лишь первую строку так сделать, чтобы попытка передать класс T без нужного атрибута вызывала ошибку компиляции вместо рантайм-ошибки. Т.е. чтобы просматривался каким-то образом контракт, облегчающий жизнь "тем, кто будет после нас".
Re[9]: Изменяемый конст? Виртуальный статик? Как выкрутиться?
Здравствуйте, Mr.Delphist, Вы писали:
MD>Хотелось бы лишь первую строку так сделать, чтобы попытка передать класс T без нужного атрибута вызывала ошибку компиляции вместо рантайм-ошибки. Т.е. чтобы просматривался каким-то образом контракт, облегчающий жизнь "тем, кто будет после нас".
С этого и надо было начинать Тот же код, но в профиль.
Разумеется, в лямбде можно опечататься и указать другой тип, но тут вариантов особых нет. Или не проверяем вообще, или выносим в интерфейс, или способ выше, или пишем roslyn analyser. Собственно всё.