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")) уже тоже примелькалось и вы сами без труда напишете метод OkOrNotFoundPaginate
Пожалуй, не бывает приложений, работающих с данными без постраничного вывода.
Вместо:
.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()TnullptrMaybe<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#, у него есть рефлексия, деревья выражений и кодогенерация, и тут можно очень много хорошего наворотить.