機能的なC#

C#はマルチパラダイム言語です。 最近、ロールは機能主義に向かって概説されました。 さらに進んで、いくつかの拡張メソッドを追加して、F#の領域に「クロール」することなく、より少ないコードを記述することができます。



Pipeto



これまでのところ、 パイプオペレーターは次のリリースに含まれません。 まあ、あなたはメソッドでうまくいくことができます。



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実行フローでは、厳密に左から右、上から下に流れます。



どちらか



現実の世界では、アルゴリズムにはしばしば線形のものよりも分岐が含まれます。



 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



に追加するIActionResult







やる



コントローラーの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による副作用は、コードのレビュー中に必ず目を引くことになります。 一般に、Doの使用は非常に物議を醸す考えです。


バイド



繰り返し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



メソッドを書くことができると思います



ページネーション



おそらく、ページネーションなしでデータを操作するアプリケーションはありません。



代わりに:



 .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



ここまで読んだ場合、LINQ式でWhereとOrderByを別々に構成するというアイデアが気に入るかもしれません。



 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);
      
      





Select



呼び出す前に、また時には後にWhere



を使用するのが理にかなっています。 IQueryableSpecification



Expression<Func<T, bool>>



両方でMaybeWhere



メソッドを追加し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);
      
      





1つのオブジェクトで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をサポートするすべてのデータソースのフィルター処理、並べ替え、およびページ分割を提供するメソッドの3行のコードを取得します。



 public IActionResult Get(MyNiceSpec spec) => query .Paged<int, MyNiceEntity, MyNiceDto>(spec) .PipeTo(Ok);
      
      





残念ながら、C#のメソッドシグネチャは、ジェネリックが豊富にあるために巨大です。 幸いなことに、アプリケーションコードでは、メソッドのパラメーターを省略できます。 LINQ拡張署名は次のようになります。 Select



から返されるタイプはどのくらいの頻度で指定しますか? この苦しみから私たちを救ったvar



に称賛を。



All Articles