LINQ 포맷터

포맷은 일반적인 작업입니다. 일반적으로 동일한 규칙을 복사/붙여넣기할 필요 없이 시스템 전체에서 동일한 서식 지정 규칙을 사용하려고 합니다. 이것이 C#에서 쉽게 할 수 있는 방법입니다.

우선 메모리에 있는 개체나 데이터베이스 쿼리에 서식 규칙을 적용할 수 있습니다. 이상적으로는 도구가 두 시나리오를 모두 지원해야 합니다. 운 좋게도 표현 트리가 우리를 위해 일할 수 있습니다. 다음과 같은 코드를 작성할 때:

queryable.Select(x => x.FirstName  + " " + x.LastName).ToList();
// Expression<Func<Employee, String>>


그리고

enumerable.Select(x => x.FirstName  + " " + x.LastName).ToList();
// Func<Employee, String>

x => x.Name 람다의 유형은 처음에는 동일하게 보이지만 서로 다릅니다. 원하는 경우 차이점을 더 잘 이해하려면 내 이야기를 확인하십시오.

목적을 위해 Expression<Func<Employee, String>> 유형이 필요합니다.

public class Formatter<T>
{
   private readonly Expression<Func<T, string>> _expression;

   public Formatter(Expression<Func<T, string>> expression)
   {
       _expression = expression
       ?? throw new ArgumentNullException(nameof(expression));
   }

    public static implicit operator
    Formatter<T>(Expression<Func<T, string>> expresssion)=>
    new Formatter<T>(expresssion);

    public static implicit operator
    Expression<Func<T, string>>(Formatter<T> formatter) =>
    formatter._expression;

    public string Format(T obj) =>
    (_func ?? (_func = _expression.Compile())).Invoke(obj);
}


여기서는 해당 식에서/로의 변환을 재정의하므로 둘 다:

Formatter<Employee> formatter =
  (Expression<Func<Employee, String>>)
  x => x.FirstName  + " " + x.LastName;


그리고

queryable.Select(formatter).ToList();


잘 작동합니다.

메모리 내 개체에서 사용하려면 Format 함수를 사용합니다.

string formattedLastName = formatter.Format(obj);


계층 관리



여태까지는 그런대로 잘됐다. 그러나 직원 계정에 동일한 형식을 사용해야 하는 경우에는 어떻게 해야 합니까?

public class Account 
{
    public Employee Employee { get; set; }
}


다음과 같이 사용할 수 있습니다.

accounts.Select(x => employeeFormatter.Format(x.Employee));


그러나 이 코드는 형식 메시지의 본문을 포함하는 표현식이 필요한 동안 캡처 InvocationExpression 하기 때문에 제대로 변환되지 않습니다. 간단히 말해서 대상 표현식에서 다른 표현식을 빌드하는 방법이 필요합니다.

public Formatter<TParent> From<TParent
  (Expression<Func<TParent, T>> map) =>
  new Formatter<TParent>(
    Expression.Lambda<Func<TParent, string>>(
      Expression.Invoke(_expression, map.Body),
      map.Parameters.First()));


이 문단이 이해가 안 되시면 체크해주세요. 여기에는 약간의 코드 연결이 필요하므로 이 문제에 대한 간단한 설명이 없습니다.

이제 From 메서드를 사용하여 Employee 포맷터를 사용하여 AccountFormatter를 빌드할 수 있습니다.

var employeeFormatter = (Expression<Func<Employee, String>>)
    x => x.FirstName  + " " + x.LastName;

var accountFormatter = 
    employeeFormatter.From(x => x.Employee);

accounts.Select(accountFormatter).ToList();


데이터 매퍼(Automapper/Mapster 등)



데이터 매퍼의 팬이라면 추가 확장 방법으로 구현을 향상시킬 수 있습니다. 다음은 AutoMapper의 예입니다.

public static class AutomapperFormatterExtensions
{
   public static void FormatWith<TSource, TDest>(
       this IMemberConfigurationExpression<TSource, TDest, string> mapperConfiguration,
       Formatter<TSource> formatter) =>
       mapperConfiguration.MapFrom<string>(formatter);

   public static void FormatWith<TSource, TDest>(
       this IPathConfigurationExpression<TSource, TDest, string> mapperConfiguration,
       Formatter<TSource> formatter) =>
       mapperConfiguration.MapFrom<string>(formatter);

   public static Action<IMemberConfigurationExpression<TSource, TDest, string>> ToMapping<TSource, TDest>(
       this Formatter<TSource> formatter) =>
       x => x.FormatWith(formatter);

   public static IMappingExpression<TSource, TDestination> ForMember<TSource, TDestination>(
       this IMappingExpression<TSource, TDestination> mappingExpression,
       Expression<Func<TDestination, string>> destinationMember,
       Formatter<TSource> formatter) =>
       mappingExpression.ForMember(
         destinationMember,
         formatter.ToMapping<TSource, TDestination>());

   public static IMappingExpression<TSource, TDestination> ForName<TSource, TDestination>(
       this IMappingExpression<TSource, TDestination> mappingExpression,
       Formatter<TSource> formatter) =>
       where TDestination : IHasName
       mappingExpression.ForMember(
         x => x.Name,
         formatter.ToMapping<TSource, TDestination>());
}


완전한 솔루션은 다음과 같습니다.

public class Formatter<T>
{
   private readonly Expression<Func<T, string>> _expression;
   private Func<T, string> _func { get; set; }

   public Formatter(Expression<Func<T, string>> expression)
   {
       _expression = expression ?? throw new ArgumentNullException(nameof(expression));
   }

   public Formatter<TParent> From<TParent>(Expression<Func<TParent, T>> map)
       => new Formatter<TParent>(Expression.Lambda<Func<TParent, string>>(
           Expression.Invoke(_expression, map.Body), map.Parameters.First()));

   public static implicit operator Formatter<T>(Expression<Func<T, string>> expresssion) =>
       new Formatter<T>(expresssion);

   public static implicit operator Expression<Func<T, string>>(Formatter<T> formatter) => formatter._expression;

   public string Format(T obj) => (_func ?? (_func = _expression.AsFunc())).Invoke(obj);
}

public static class AutomapperFormatterExtensions
{
   public static void FormatWith<TSource, TDest>(
       this IMemberConfigurationExpression<TSource, TDest, string> mapperConfiguration,
       Formatter<TSource> formatter) =>
       mapperConfiguration.MapFrom<string>(formatter);

   public static void FormatWith<TSource, TDest>(
       this IPathConfigurationExpression<TSource, TDest, string> mapperConfiguration,
       Formatter<TSource> formatter) =>
       mapperConfiguration.MapFrom<string>(formatter);

   public static Action<IMemberConfigurationExpression<TSource, TDest, string>> ToMapping<TSource, TDest>(
       this Formatter<TSource> formatter) =>
       x => x.FormatWith(formatter);

   public static IMappingExpression<TSource, TDestination> ForMember<TSource, TDestination>(
       this IMappingExpression<TSource, TDestination> mappingExpression,
       Expression<Func<TDestination, string>> destinationMember,
       Formatter<TSource> formatter) =>
       mappingExpression.ForMember(destinationMember, formatter.ToMapping<TSource, TDestination>());

   public static IMappingExpression<TSource, TDestination> ForName<TSource, TDestination>(
       this IMappingExpression<TSource, TDestination> mappingExpression,
       Formatter<TSource> formatter) =>
       where TDestination : IHasName
       mappingExpression.ForMember(x => x.Name, formatter.ToMapping<TSource, TDestination>());
}



즐거운 코딩하세요!

좋은 웹페이지 즐겨찾기