Как осуществить рефакторинг, это копировало код LINQ?

Я пытаюсь выяснить, как осуществить рефакторинг этот код LINQ приятно. Этот код и другой подобный код повторяются в том же файле, а также в других файлах. Когда-то управляемые данные идентичны, и иногда изменения данных и логика остаются тем же.

Вот пример дублированной логики, воздействующей на различные поля различных объектов.

public IEnumerable<FooDataItem> GetDataItemsByColor(IEnumerable<BarDto> dtos)
{
    double totalNumber = dtos.Where(x => x.Color != null).Sum(p => p.Number);
    return from stat in dtos
           where stat.Color != null
           group stat by stat.Color into gr
           orderby gr.Sum(p => p.Number) descending
           select new FooDataItem
           {
               Color = gr.Key,
               NumberTotal = gr.Sum(p => p.Number),
               NumberPercentage = gr.Sum(p => p.Number) / totalNumber
           };
}

public IEnumerable<FooDataItem> GetDataItemsByName(IEnumerable<BarDto> dtos)
{
    double totalData = dtos.Where(x => x.Name != null).Sum(v => v.Data);
    return from stat in dtos
           where stat.Name != null
           group stat by stat.Name into gr
           orderby gr.Sum(v => v.Data) descending
           select new FooDataItem
           {
               Name = gr.Key,
               DataTotal = gr.Sum(v => v.Data),
               DataPercentage = gr.Sum(v => v.Data) / totalData
           };
}

У кого-либо есть хороший способ осуществить рефакторинг это?

9
задан HugoRune 17 April 2012 в 22:42
поделиться

5 ответов

Что-то вроде этого:

public IEnumerable<FooDataItem> GetDataItems<T>(IEnumerable<BarDto> dtos,
    Func<BarDto, T> groupCriteria,
    Func<BarDto, double> dataSelector,
    Func<T, double, double, FooDataItem> resultFactory)
{
    var validDtos = dtos.Where(d => groupCriteria(d) != null);
    double totalNumber = validDtos.Sum(dataSelector);

    return validDtos
        .GroupBy(groupCriteria)
        .OrderBy(g => g.Sum(dataSelector))
        .Select(gr => resultFactory(gr.Key,
                                    gr.Sum(dataSelector),
                                    gr.Sum(dataSelector) / totalNumber));
}

В вашем примере это можно назвать так:

GetDataItems(
    x => x.Color,  // the grouping criterion
    x => x.Number, // the value criterion
    (key, total, pct) =>
        new FooDataItem {
            Color = key, NumberTotal = total, NumberPercentage = pct });

Если вы измените FooDataItem на более общий, это будет проще.

11
ответ дан 4 December 2019 в 11:40
поделиться

Вот метод расширения, который выделяет аналогичные части каждого запроса:

public static IEnumerable<TDataItem> GetDataItems<TData, TDataItem>(
    this IEnumerable<BarDto> dtos,
    Func<BarDto, TData> dataSelector,
    Func<BarDto, double> numberSelector,
    Func<TData, double, double, TDataItem> createDataItem)
    where TData : class
{
    var eligibleDtos = dtos.Where(dto => dataSelector(dto) != null);

    var totalNumber = eligibleDtos.Sum(numberSelector);

    return
        from dto in eligibleDtos
        group dto by dataSelector(dto) into dtoGroup
        let groupNumber = dtoGroup.Sum(numberSelector)
        orderby groupNumber descending
        select createDataItem(dtoGroup.Key, groupNumber, groupNumber / totalNumber);
}

Вы можете использовать его так:

var itemsByName = dtos.GetDataItems(
    dto => dto.Name,
    dto => dto.Data,
    (name, groupTotal, groupPercentage) => new FooDataItem
    {
        Name = name,
        NumberTotal = groupTotal,
        NumberPercentage = groupPercentage
    });

var itemsByColor = dtos.GetDataItems(
    dto => dto.Color,
    dto => dto.Number,
    (color, groupTotal, groupPercentage) => new FooDataItem
    {
        Color = color,
        DataTotal = groupTotal,
        DataPercentage = groupPercentage
    });
1
ответ дан 4 December 2019 в 11:40
поделиться

Я думаю, что если бы вы отредактировали это, было бы труднее читать, чем то, что у вас уже есть. Все, что я могу придумать, связано либо с динамическим Linq, либо с изменением или инкапсуляцией BarDto, чтобы иметь какой-то специализированный элемент, который можно использовать только для группировки.

1
ответ дан 4 December 2019 в 11:40
поделиться

Я бы не стал использовать для этого синтаксис запроса, используйте цепочку методов.

public IEnumerable<FooDataItem> GetDataItems(IEnumerable<BarDto> dtos, Func<BarDto, object> key, Func<BarDto, object> data)
{
  double totalData = dtos.Where(d => key(d) != null).Sum(data);
  return dtos.Where(d => key(d) != null)
             .GroupBy(key)
             .OrderBy(d => d.Sum(data))
             .Select(
               o => new FooDataItem() 
               { 
                 Key = o.Key, 
                 Total = o.Sum(data), 
                 Percentage = o.sum(data) / totalData
               });
}

(написано без компилятора и т. Д.).

Лично я бы не стал его реорганизовывать, так как это сделает код менее читаемым и понятным.

3
ответ дан 4 December 2019 в 11:40
поделиться

Вам нужно будет переключиться с выражения запроса и преобразовать все ваши предложения where, group by, order by и select в лямбда-выражения. Затем вы можете создать функцию, которая принимает каждый из них в качестве параметров. Вот пример:

private static IEnumerable<FooDataItem> GetData<T>(IEnumerable<Foo> foos, Func<Foo, bool> where, Func<Foo, T> groupby, Func<IGrouping<T, Foo>, T> orderby, Func<IGrouping<T, Foo>, FooDataItem> select)
{
    var query = foos.Where(where).GroupBy(groupby).OrderBy(orderby).Select(select);
    return query;
}

На основе этого кода

class Foo
{
    public int Id { get; set; }
    public int Bar { get; set; }
}

...

List<Foo> foos = new List<Foo>(); // populate somewhere

Func<Foo, bool> where = f => f.Id > 0;
Func<Foo, int> groupby = f => f.Id;
Func<IGrouping<int, Foo>, int> orderby = g => g.Sum(f => f.Bar);
Func<IGrouping<int, Foo>, FooDataItem> select = g => new FooDataItem { Key = g.Key, BarTotal = g.Sum(f => f.Bar) };

var query = GetData(foos, where, groupby, orderby, select);
2
ответ дан 4 December 2019 в 11:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: