PipeTo
Пока Pipe Operator не собираются включать в следующий релиз. Что-ж, можно обойтись и методом.
public static TResult PipeTo<TSource, TResult>(
this TSource source, Func<TSource, TResult> func)
=> func(source);
Императивный вариант
public IActionResult Get()
{
var someData = query
.Where(x => x.IsActive)
.OrderBy(x => x.Id)
.ToArray();
return Ok(someData);
}
С PipeTo
public IActionResult Get() => query
.Where(x => x.IsActive)
.OrderBy(x => x.Id)
.ToArray()
.PipeTo(Ok);
Заметили? В первом варианте мне нужно было вернуть взгляд к объявлению переменной и потом перейти к Ok. С PipeTo execution-flow строго слева-направо, сверху-вниз.
Either
В реальном мире алгоритмы чаще содержат ветвления, чем бывают линейными:
public IActionResult Get(int id) => query
.Where(x => x.Id == id)
.SingleOrDefault()
.PipeTo(x => x != null ? Ok(x) : new NotFoundResult(“Not Found”));
Выглядит уже не так хорошо. Исправим это с помощью метода
Either
:public static TOutput Either<TInput, TOutput>(this TInput o, Func<TInput, bool> condition,
Func<TInput, TOutput> ifTrue, Func<TInput, TOutput> ifFalse)
=> condition(o) ? ifTrue(o) : ifFalse(o);
public IActionResult Get(int id) => query
.Where(x => x.Id == id)
.SingleOrDefault()
.Either(x => x != null, Ok, _ => (IActionResult)new NotFoundResult("Not Found"));
Добавим перегрузку с проверкой на null:
public static TOutput Either<TInput, TOutput>(this TInput o, Func<TInput, TOutput> ifTrue,
Func<TInput, TOutput> ifFalse)
=> o.Either(x => x != null, ifTrue, ifFalse);
public IActionResult Get(int id) => query
.Where(x => x.Id == id)
.SingleOrDefault()
.Either(Ok, _ => (IActionResult)new NotFoundResult("Not Found"));
К сожалению вывод типов в C# еще не идеален, поэтому пришлось добавить явный каст к
IActionResult
.Do
Get-методы контроллеров не должны создавать побочных эффектов, но иногда «очень надо».
public static T Do<T>(this T obj, Action<T> action)
{
if (obj != null)
{
action(obj);
}
return obj;
}
public IActionResult Get(int id) => query
.Where(x => x.Id == id)
.Do(x => ViewBag.Title = x.Name)
.SingleOrDefault()
.Either(Ok, _ => (IActionResult)new NotFoundResult("Not Found"));
При такой организации кода побочный эффект с Do обязательно бросится в глаза во время code review. Хотя в целом использование Do — очень спорная идея.
ById
Не находите, что повторять постоянно
q.Where(x => x.Id == id).SingleOrDefault()
муторно?public static TEntity ById<TKey, TEntity>(this IQueryable<TEntity> queryable, TKey id)
where TEntity : class, IHasId<TKey> where TKey : IComparable, IComparable<TKey>, IEquatable<TKey>
=> queryable.SingleOrDefault(x => x.Id.Equals(id));
public IActionResult Get(int id) => query
.ById(id)
.Do(x => ViewBag.Title = x.Name)
.Either(Ok, _ => (IActionResult)new NotFoundResult("Not Found"));
А если, я не хочу получать сущность целиком и мне нужна проекция:
public static TProjection ById<TKey, TEntity, TProjection>(this IQueryable<TEntity> queryable, TKey id,
Expression<Func<TEntity, TProjection>> projectionExpression)
where TKey : IComparable, IComparable<TKey>, IEquatable<TKey>
where TEntity : class, IHasId<TKey>
where TProjection : class, IHasId<TKey>
=> queryable.Select(projectionExpression).SingleOrDefault(x => x.Id.Equals(id));
public IActionResult Get(int id) => query
.ById(id, x => new {Id = x.Id, Name = x.Name, Data = x.Data})
.Do(x => ViewBag.Title = x.Name)
.Either(Ok, _ => (IActionResult)new NotFoundResult("Not Found"));
Я думаю, что к текущему моменту
(IActionResult)new NotFoundResult("Not Found"))
уже тоже примелькалось и вы сами без труда напишете метод OkOrNotFound
Paginate
Пожалуй, не бывает приложений, работающих с данными без постраничного вывода.
Вместо:
.Skip((paging.Page - 1) * paging.Take)
.Take(paging.Take);
Можно сделать так:
public interface IPagedEnumerable<out T> : IEnumerable<T>
{
long TotalCount { get; }
}
public static IQueryable<T> Paginate<T>(this IOrderedQueryable<T> queryable, IPaging paging)
=> queryable
.Skip((paging.Page - 1) * paging.Take)
.Take(paging.Take);
public static IPagedEnumerable<T> ToPagedEnumerable<T>(this IOrderedQueryable<T> queryable,
IPaging paging)
where T : class
=> From(queryable.Paginate(paging).ToArray(), queryable.Count());
public static IPagedEnumerable<T> From<T>(IEnumerable<T> inner, int totalCount)
=> new PagedEnumerable<T>(inner, totalCount);
public IActionResult Get(IPaging paging) => query
.Where(x => x.IsActive)
.OrderBy(x => x.Id)
.ToPagedEnumerable(paging)
.PipeTo(Ok);
IQueryableSpecification IQueryableFilter
Если вы дочитали до этого места, возможно, Вам понравится идея по другому компоновать Where и OrderBy в LINQ выражениях:
public class MyNiceSpec : AutoSpec<MyNiceEntity>
{
public int? Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public string Description { get; set; }
}
public IActionResult Get(MyNiceSpec spec) => query
.Where(spec)
.OrderBy(spec)
.ToPagedEnumerable(paging)
.PipeTo(Ok);
При этом иногда имеет смысл применять
Where
до вызова Select
, а иногда — после. Добавим метод MaybeWhere
, который сможет работать как с IQueryableSpecification
, так и с Expression<Func<T, bool>>
public static IQueryable<T> MaybeWhere<T>(this IQueryable<T> source, object spec)
where T : class
{
var specification = spec as IQueryableSpecification<T>;
if (specification != null)
{
source = specification.Apply(source);
}
var expr = spec as Expression<Func<T, bool>>;
if (expr != null)
{
source = source.Where(expr);
}
return source;
}
И теперь можно написать метод, учитывающий разные варианты:
public static IPagedEnumerable<TDest> Paged<TEntity, TDest>(
this IQueryableProvider queryableProvider, IPaging spec ,
Expression<Func<TEntity, TDest>> projectionExpression)
where TEntity : class, IHasId
where TDest : class, IHasId
=> queryableProvider
.Query<TEntity>()
.MaybeWhere(spec)
.Select(projectionExpression)
.MaybeWhere(spec)
.MaybeOrderBy(spec)
.OrderByIdIfNotOrdered()
.ToPagedEnumerable(spec);
Или с применением Queryable Extensions AutoMapper:
public static IPagedEnumerable<TDest> Paged<TEntity, TDest>(this IQueryableProvider queryableProvider,
IPaging spec)
where TEntity : class, IHasId
where TDest : class, IHasId => queryableProvider
.Query<TEntity>()
.MaybeWhere(spec)
.ProjectTo<TDest>()
.MaybeWhere(spec)
.MaybeOrderBy(spec)
.OrderByIdIfNotOrdered()
.ToPagedEnumerable(spec);
Если вы считаете, что лепить
IPaging
, IQueryableSpecififcation
и IQueryableOrderBy
на один объект богомерзко, то ваш вариант такой:public static IPagedEnumerable<TDest> Paged<TEntity, TDest>(this IQueryableProvider queryableProvider,
IPaging paging, IQueryableOrderBy<TDest> queryableOrderBy,
IQueryableSpecification<TEntity> entitySpec = null, IQueryableSpecification<TDest> destSpec = null)
where TEntity : class, IHasId where TDest : class
=> queryableProvider
.Query<TEntity>()
.EitherOrSelf(entitySpec, x => x.Where(entitySpec))
.ProjectTo<TDest>()
.EitherOrSelf(destSpec, x => x.Where(destSpec))
.OrderBy(queryableOrderBy)
.ToPagedEnumerable(paging);
В итоге получаем три строчки кода для метода, который фильтрует, сортирует и обеспечивает постраничный вывод для любых источников данных с поддержкой LINQ.
public IActionResult Get(MyNiceSpec spec) => query
.Paged<int, MyNiceEntity, MyNiceDto>(spec)
.PipeTo(Ok);
К сожалению сигнатуры методов в C# выглядят монструозно из-за обилия generic'ов. К счастью, в прикладном коде параметры методов можно опустить. Сигнатуры extension'ов LINQ выглядят примерно также. Как часто вы указываете возвращаемый из
Select
тип? Хвала var
, который избавил нас от этого мучения.Комментарии (58)
Daniro_San
31.03.2017 02:48+3Враперы-оберточки, хоп абстракций кусочечки…
Польза таких избыточных абстракций в C# сомнительна, но чего греха таить, меня тоже на такие оберточные поделия часто прорывает)
public IActionResult Get(int id) => query .Where(x => x.Id == id) .SingleOrDefault() .PipeTo(x => x != null ? Ok(x) : new NotFoundResult(“Not Found”));
Выглядит уже не так хорошо. Исправим это с помощью метода Either:
public static TOutput Either<TInput, TOutput>(this TInput o, Func<TInput, bool> condition, Func<TInput, TOutput> ifTrue, Func<TInput, TOutput> ifFalse) => condition(o) ? ifTrue(o) : ifFalse(o); public IActionResult Get(int id) => query .Where(x => x.Id == id) .SingleOrDefault() .Either(x => x != null, Ok, _ => (IActionResult)new NotFoundResult("Not Found"));
Стало ничем не лучше, даже наоборот, переусложнён код, который ещё может быть кому-то предстоит отлаживать…Deosis
31.03.2017 08:07+1Начали за здравие, закончили за упокой.
PipeTo, Do ещё приемлемы, а все остальное выглядит как функциональщина ради функциональщины:
Either — заменили? и: на, и,
ById — теперь надо реализовать ещё один интерфейс.
ById с проекцией — если вставить перед ним Select будет понятнее.
ToPagedEnumerable — не ленивая
IQueryableSpecification — В каком порядке отсортирует? сначала по Id потом по Name или наоборот?
В случае с лямбдой все понятно, а компилятор эту «спецификацию» создаст за нас.
MaybeWhere — зачем проверять на Expression, если мы передаем всегда IPaging
Проще реализовать два метода расширения и переложить работу на компилятор.
ПС: Не написали про методы MinBy, MaxBy.
Также полезным был бы интерфейс:
interface ICountedEnumerable<out T> : IEnumerable<T> { public int Count {get;} }
Как промежуточный между ICollection и IEnumerable, расширения которого избавляют от лишней работы в методах ToArray, ToList.
ППС: И напоследок пару расширений:
IEnumerable<Tuple<T, T>> Pack<T>(this IEnumerable<T> source); IEnumerable<Tuple<T, T>> Window<T>(this IEnumerable<T> source);
marshinov
31.03.2017 11:51Either — заменили? и: на, и,
Я использую только вторую перегрузку (та, что проверяет на null). Получается так:
str.Either(x => x + " Some more text", _ => "No Text");
. Возможно, первую, та что с condition можно и вправду удалить за ненадобностью. Подумаю об этом.
IQueryableSpecification — В каком порядке отсортирует? сначала по Id потом по Name или наоборот?
Подробнее эта часть описана в другой статье. Эта ссылка есть и в тексте этого топика. Скорее всего вы не перешли, поэтому возник вопрос.
В случае с лямбдой все понятно, а компилятор эту «спецификацию» создаст за нас.
MaybeWhere — зачем проверять на Expression, если мы передаем всегда IPaging
Посмотрите на метод
Проще реализовать два метода расширения и переложить работу на компилятор.Paged
. MaybeWhere нужен только для компоновки. Возможно его стоит сделать приватным.areht
02.04.2017 00:14> Я использую только вторую перегрузку (та, что проверяет на null).
Конечно, если использовать как
> .SingleOrDefault().Either(x => x != null, Ok, _ => (IActionResult)new NotFoundResult(«Not Found»));
Есть какой-то сакральный смысл в том, что бы сначала самому себе подсовывать Default, а потом вот так изгаляться?
> .SingleOrElse(new NotFoundResult(«Not Found»));
petuhov_k
31.03.2017 05:51+3Всё это здорово, пока вы единственный разработчик и помните, что вы спрятали за этими 'PipeTo' и пр. Но отлаживать чужой подобный код то ещё удовольствие. В каждый такой extension method всё равно приходится залезать чтобы понять, делает ли он то, о чём вы подумали или, вообще, что именно он делает. А попробовав F# можно понять, что «крен функциональщины» в C# так себе.
Win332
31.03.2017 08:57-1Для ловли ошибок есть юнит тесты. А данный подход на оборот приятен, удобен и сокращает код.
petuhov_k
31.03.2017 09:36Наличие юниттестов не исключает отладку, а даже наоборот. Насчет удобства, кому как. Я поделился своим опытом.
Odrin
31.03.2017 10:20+3Ну ок, тест упал, в тестируемом коде ошибка. Это не избавляет от необходимости проанализировать код, а следовательно разобраться во всей этой кастомщине.
YuryZakharov
31.03.2017 08:57+1Можно позанудствовать?
Спасибо.
ById требует соблюдения некоторых соглашений. Что-то облегчили, в чем-то добавили сложности
Ваш Either не совсем та структура (монада, если угодно) Either, которая обычно используется в ФП…
Ну, и названия типа MaybeWhere глаз немного режут.
А вообще радует Ваш подходmarshinov
31.03.2017 09:10MaybeWhere мне потребовался, чтобы написать
query .MaybeWhere(/* может быть ты хочешь применить where до проекции*/) .Select (...) .MaybeWhere(/* или после?*/) .ToArray()
Тогда можно лениться и делать так:
public class Spec: IQueryableSpec<TEntity>, IQueryableSpec<TProjection>
В таком случае будут применены оба Where. Обычно два не требуется — достаточно одного. Специально для этого в самом конце есть Paged с явным указанием параметров. Мне в простых Crud проще вешать много интерфейсов на один класс. Главное понимать Flow:Entity => Dto
. Соовтетственно фильтрации и сортировки применяются в таком-же порядке.
Either
здесь действительно не имеет отношения к слове на букву М:) На данный момент я думаю, что в C# проще кидать исключения и писать только один try/catch на все приложение. Получится вполне себеEither<TResult, Exception>
. Пытаться конвертнуть все Exception'ы в Failure в .NET Framework — задача так себе.
По поводу читабельности мы специально проводили эксперимент и давали нескольким программистам посмотреть код сEither
иPipeTo
. Все правильно ответили как работает код, поэтому оставили название методов такими. Можете предложить название лучше?
YuryZakharov
31.03.2017 10:57+1Either и PipeTo
Можете предложить название лучше?
Ну, Either тут больше похож на ContinueWith, не? На мой взгляд. Не претендую. Просто CPS, как он есть…
По поводуEither<TResult, Exception>
— дело вкуса, конечно. Я, например, в одном из проектов использую Either<TError, TResult>. И так это меня радует, неимоверно. Почти как у взрослых :), вынуждает разработчика обрабатывать оба варианта, иначе не скомпилируется.marshinov
31.03.2017 11:02ContinueWith
— вариант, но тогда придется пилить нечто вроде
flow.ContinueWith(x => ...).Error(e => ...)
. А это уже надо опять промежуточный объект создавать и получится тот-же самыйEither<TResult, Exception>
.
А не поделитесь вашей реализациейEither
? Я написал одну, показалось не элегантно и отказался. Может есть реализации лучше моей?YuryZakharov
31.03.2017 12:20+1Не то, чтобы моя реализация с нуля:
Как-то такpublic class Either<TL, TR> { [DataMember] private readonly bool _isLeft; [DataMember] private readonly TL _left; [DataMember] private readonly TR _right; public Either(TL left) { _left = left; _isLeft = true; } public Either(TR right) { _right = right; _isLeft = false; } /// <summary> /// Checks the type of the value held and invokes the matching handler function. /// </summary> /// <typeparam name="T">The return type of the handler functions.</typeparam> /// <param name="ofLeft">Handler for the Left type.</param> /// <param name="ofRight">Handler for the Right type.</param> /// <returns>The value returned by the invoked handler function.</returns> /// <exception cref="System.ArgumentNullException"> /// </exception> public T Match<T>(Func<TL, T> ofLeft, Func<TR, T> ofRight) { if (ofLeft == null) { throw new ArgumentNullException(nameof(ofLeft)); } if (ofRight == null) { throw new ArgumentNullException(nameof(ofRight)); } return _isLeft ? ofLeft(_left) : ofRight(_right); } /// <summary> /// Checks the type of the value held and invokes the matching handler function. /// </summary> /// <param name="ofLeft">Handler for the Left type.</param> /// <param name="ofRight">Handler for the Right type.</param> /// <exception cref="System.ArgumentNullException"> /// </exception> public void Match(Action<TL> ofLeft, Action<TR> ofRight) { if (ofLeft == null) { throw new ArgumentNullException(nameof(ofLeft)); } if (ofRight == null) { throw new ArgumentNullException(nameof(ofRight)); } if (_isLeft) { ofLeft(_left); } else { ofRight(_right); } } public TL LeftOrDefault() => Match(l => l, r => default(TL)); public TR RightOrDefault() => Match(l => default(TR), r => r); public Either<TR, TL> Swap() => Match((Func<TL, Either<TR, TL>>) (Right<TR, TL>), Left<TR, TL>); public Either<TL, T> Bind<T>(Func<TR, T> f) => BindMany(x => Right<TL, T>(f(x))); public Either<TL, T> BindMany<T>(Func<TR, Either<TL, T>> f) => Match(Left<TL, T>, f); public Either<TL, TResult> BindMany<T, TResult>(Func<TR, Either<TL, T>> f, Func<TR, T, TResult> selector) => BindMany(x => f(x).Bind(t => selector(_right, t))); public static implicit operator Either<TL, TR>(TL left) => new Either<TL, TR>(left); public static implicit operator Either<TL, TR>(TR right) => new Either<TL, TR>(right); public static Either<TLeft, TRight> Left<TLeft, TRight>(TLeft left) => new Either<TLeft, TRight>(left); public static Either<TLeft, TRight> Right<TLeft, TRight>(TRight right) => new Either<TLeft, TRight>(right); public static Either<Exception, T> Try<T>(Func<T> f) { try { return new Either<Exception, T>(f.Invoke()); } catch (Exception ex) { return new Either<Exception, T>(ex); } } }
marshinov
31.03.2017 13:21А прикладной код как выглядит?
YuryZakharov
31.03.2017 13:50Примерно так:public interface ISomeService { Task<Either<TradeError, Quote>> GetQuoteAsync(GetQuoteQuery query); } ... var result = await _service.GetQuoteAsync(query); result.Match(SetQuoteError, SetQuote); ... private void SetQuoteError(TradeError error) { // do something } private void SetQuote(Quote quote) { // do something }
marshinov
01.04.2017 13:08А чем это лучше использования
task.IsFaulted
?
<оффтоп>У вас на работе трейдинг что-ли (трейд эрроры, котировки)?:)</оффтоп>YuryZakharov
03.04.2017 10:55А потому, что Error возвращает удаленный сервер, вполне легитимный ответ и его надо обрабатывать. А Task может завалиться и по другой причине.
Что-то вроде :)
oxidmod
31.03.2017 09:50+2Почему бы просто не упороться в функциональный язык?
YuryZakharov
31.03.2017 11:00Да хотя бы, чтобы не переучивать всю команду в середине проекта.
А так — здесь им идейку подкинул, тут структурку, глядишь, а они уже и сами упоролись куда надо.
FadeToBlack
31.03.2017 13:21+1Что это за новая мода — писать всю программу в одну строчку, а потом делать в ней переносы, когда она не входит в экран? Ладно еще запросы linq — это еще куда ни шло, но ветвления? Вы серьезно?
Ogoun
31.03.2017 13:31В условиях предпочитаю сначала писать rvalue:
public static TInput Do<TInput>(this TInput o, Action<TInput> action) { if (null != o) action(o); return o; }
Кроме того что это исключает тривиальные ошибки присваивания в условии, такой подход позволит применять функции и к структурам, хотя для них проверка на null и бессмысленна, но бывает заранее неизвестно что придется обрабатывать.YuryZakharov
31.03.2017 13:51+2Тяжелое плюсовое детство?
FadeToBlack
31.03.2017 14:03+1Даже на плюсах так никто уже не пишет.
Ogoun
31.03.2017 16:08В шарпе присваивание в условии вполне может быть
int a = 1; if ((a = 2) == 3) { a = 4; }
В чем минус указанного подхода (с rvalue в первом операнде)?YuryZakharov
31.03.2017 16:45А в чем преимущество присваивания в условии?
Ogoun
31.03.2017 16:53Все зависит от контекста. Например, при чтении из файла может быть удобно писать
while(null != (line = stream.ReadLine())) { }
Вместо
line = stream.ReadLine(); while(null != line) { /* ToDo */ line = stream.ReadLine(); }
Но мой вопрос был, в чем минус подхода в следующей записи:
if(null != expression)
При такой же читабельности получаем как плюс, исключение ошибки присваивания, и возможность единообразного использования как reference так и value типов.YuryZakharov
31.03.2017 17:31То есть, стараться всунуть как можно больше в одну строчку, получая потенциальный рассадник ошибок и ухудшение понятности, это улучшение?
Не говоря уже о такой «ошибке присваивания, какnull = line;
Гипотетически, если тип приводится к bool, можно наворотить, но Вы же не пишете
, правда?if(b == false) { }
Ogoun
31.03.2017 17:45Да, я пишу так:
if(false == b) { }
и не пишу так
if(!b) { }
т.к. при чтении кода намного меньше бросается в глаза
Но вы отвечаете не на тот вопрос. Меня действительно интересует в чем минус, или возможная проблема случая когда в условии первым операндом стоит rvalue.YuryZakharov
31.03.2017 18:03+1Про code style, naming conventions рассказывать?
Про семантику?
Сравните:
если мое_множество равно пустому_множеству то…
против
если пустое_множество равно моему_множеству то…
пустое множество не может равняться чему-то еще, оно одно такое, инициальный объект в этом типе.
YuryZakharov
31.03.2017 18:30Выше неудачное объяснение, громоздкое.
Смотрите, Вы говорите
If(false == b) {}
false — это константа, она ничему больше, кроме самой себя, равняться не может.
С этим, надеюсь, спорить не будете?
Так вот,странносемантически неверно сравнивать константу с чем-то. Что-то с константой- да, если это что-то может принимать разные значения.
Технически, никто Вам не запрещает писать так, как Вы пишете, ни компилятор, ни рантайм. Но читается это не очень…
Поэтому, собственно, весь остальной мир на шарпе и пишет if(value==null).
Ogoun
01.04.2017 02:08По итогу, ваш аргумент следующий, так писать не стоит потому что большинство пишет по другому? И никаких побочных, кроме нарушения стиля, нет?
Из комментария выше про множества, с моей точки зрения оба утверждения семантически равноценны, т.к. операция сравнения не предполагает порядок указания сравниваемых частей.YuryZakharov
03.04.2017 10:48В том-то и дело, что не равноценны. Если Вы этого не видите, ну, что ж…
Про стиль и пальцы написали уже. У меня такое не то, что code review не прошло бы, даже commit завернуло бы.
Jofr
04.04.2017 12:45+1К сожалению, не могу вспомнить точной терминологии про смысловые ударения, но основная идея в том, что предложения «мама мыла раму» и «рама мылась мамой», с одной точки зрения, описывают один и тот же процесс, но с другой, выделяют совершенно разные центральные объекты действия.
Языки программирования — они ж для человеков, не для роботов :)
FadeToBlack
31.03.2017 19:11В том, что я бы просто за такое ломал бы пальцы. Если делаете в своем проекте — извольте, но если работаете в команде — не следует так подставлять своих товарищей.
SergeyVoyteshonok
31.03.2017 17:31+1ById тоже используем, только у нас в Code First, есть базовый абстрактный класс, от которого остальные наследуются, что-то типа:
public abstract class Identified { public long Id { get; set; } }
и уже ему пишем расширение:
public static T ById<T>(this IEnumerable<T> identifies, long id) where T : Identified { return identifies.SingleOrDefault(c=>c.Id.Equals(id)); }
Кстати, почему у вас расширение только для IQueryable? можно сразу на IEnumerable, это может помочь дальше применять ById, но просто теперь для всех перечислений с этим типом.marshinov
31.03.2017 17:50+1А у нас так:
public interface IHasId { object Id { get; } } public interface IHasId<out TKey> : IHasId where TKey: IComparable, IComparable<TKey>, IEquatable<TKey> { new TKey Id { get; } }
Для enumerable тоже бывает делаем расширения, но чаще нужны именно IQueryable.old_man_logan
01.04.2017 16:56Может, конечно, адово туплю, но для чего IHasId реализует IHasId? Честно говоря, не понимаю — зачем вообще первый интерфейс нужен.
marshinov
01.04.2017 16:58Чтобы можно было сделать каст к
IHasId
без generic'а. Бывают случаи, когда типT
не доступен.Deosis
03.04.2017 08:18+1Если Т не доступен, то можно привести к
IHasId<object>
marshinov
03.04.2017 10:44Спасибо за совет! Нужно проверить, что LINQ нормально отработает с ковариацией и выбросить без generic'а, если все ок.
marshinov
09.04.2017 18:18Вспомнил, почему сделали так. У нас на
IHasId<T>
висит whereT:IEquatable<T>
. Так что привести к object нельзя, потому что object не реализуетIEquatable
. Убирать это условие не хочется, потому что тип ключа по определению должен быть сравним (чтобы иметь возможность быть уникальным). Интерфейс безT
остался для поддержки композитных ключей.
ruslanfedoseenko
31.03.2017 20:33А это все можно использовать с Entity framework? конкретно интересует пагинация.
marshinov
31.03.2017 21:13Да, все совместимо. Можете подключать пакетом, если хотите. Там еще всякая всячина.
Magic_B
03.04.2017 14:46Вот моя библиотека для программирования на C# в функциональном стиле: github repo
Получается код типа:
public IActionResult Add(string a, string b, string c, string d, int count = 1, int mod=0) => _dal.GetUrl(a, b, c, d).Convert(_dal.GetProduct) .IfNotNull(x => _dal.AddProductToCart(_dal.GetCartBySession(HttpContext.Session.Id), x, count, mod) .Extend(x).Convert(PartialView), () => (IActionResult) NotFound());
Deosis
04.04.2017 08:33Посмотрел. Пара замечаний:
1) В методах Add, Remove есть побочные эффекты.
2) Возможность добавить/удалить проверяется через list.GetType().IsArray и при передаче ReadOnlyCollection упадет с ошибкой. Лучше проверять через свойство IsReadOnly
mezastel
В мире С++ люди еще помимо fluent interface’ов делают так: дают некоторым результатам цепочных вызовов классы-обертки, т.е. например вызов возвращает не или , а обертку . А для некоторых таких оберток генерят перегрузки операторов вроде чтобы композиция не выглядела так адово, как огромная цепочка вызовов.
Maybe()
T
nullptr
Maybe<T>
||
marshinov
В C# тоже можно так делать. А для
Maybe
еще и реализовать методSelectMany
и писать совсем странные конструкции вроде:Правда, практического применения этой конструкции мне найти не удалось :)
AxisPod
В C++ вообще можно изменить поведение так, что вообще непонятно что творит код. В своей жизни пришлось клепать аналог Expression Tree, ORM, с лютым синтаксисом и т.д… А уж если посмотреть на что-нить типа boost::spirit, boost::phoenix, то становится дурно.
Увы, C# в большинстве случаев не даст такое сделать, ибо generic != template, да и даже отсутствие typdef уже делает невозможным подобные вещи.
С другой стороны, скорость компиляции и требования к памяти оставляют желать лучшего в случае таких лютых извращений на C++.
marshinov
Да, шаблоны в плюсах — штука эпичная. Особенно ошибки компилятора в них:)
Думаю, что не «увы», а «хорошо что» :). У C# же достаточно неплохо с интероперабельностью с плюсами. Захотелось чего-нибудь эдакого — знай подключай плюсовые dll. А для типовых задач, решаемых на C# такая мощь — избыточна.
AxisPod
Со временем можно научиться в этом разбираться. И если получится разбираться в каше логов GCC, то с остальными компиляторами проблем не возникает.
Ну да, примерно это и имелось ввиду :) Пока народ не проникся Roslyn, C# будет держаться.
А в плане силы C#, у него есть рефлексия, деревья выражений и кодогенерация, и тут можно очень много хорошего наворотить.