4
\$\begingroup\$

All of the class here share these same property

Country, Total and Date.

Total property are integer in some classes and decimal in some classed.

The extension method below has a lot of duplication. i have tried to refactor it using interface but i can't find a way to refactor it at the last step on selection since it need different class

    public static class TotalApprovedPOAmountStatisticExtension
{
    public static void MergeNullAndEmplyCountry(this IEnumerable<TotalApprovedPOAmountStatistic> totalUserStatistic)
    {
        foreach (var item in totalUserStatistic)
        {
            if (string.IsNullOrWhiteSpace(item.Country))
            {
                item.Country = "";
            }
        }

        totalUserStatistic = totalUserStatistic
                            .GroupBy(item => new { item.Country, item.Date })
                            .Select(groupedUser => new TotalApprovedPOAmountStatistic() 
                            { Country = groupedUser.Key.Country, Total = groupedUser.Sum(item => item.Total), Date = groupedUser.Key.Date }).ToList();

    }
}

    public static class TotalPOStatisticListExtension
{
    public static void MergeNullAndEmplyCountry(this IEnumerable<TotalPOStatistic> totalUserStatistic)
    {
        foreach (var item in totalUserStatistic)
        {
            if (string.IsNullOrWhiteSpace(item.Country))
            {
                item.Country = "";
            }
        }

        totalUserStatistic = totalUserStatistic
                            .GroupBy(item => new { item.Country, item.Date })
                            .Select(groupedUser => new TotalPOStatistic() { Country = groupedUser.Key.Country, Total = groupedUser.Sum(item => item.Total), Date = groupedUser.Key.Date }).ToList();

    }
}

    public static class TotalProjectAmountStatisticListExtension
{
    public static void MergeNullAndEmplyCountry(this IEnumerable<TotalProjectAmountStatistic> totalUserStatistic)
    {
        foreach (var item in totalUserStatistic)
        {
            if (string.IsNullOrWhiteSpace(item.Country))
            {
                item.Country = "";
            }
        }

        totalUserStatistic = totalUserStatistic
                            .GroupBy(item => new { item.Country, item.Date })
                            .Select(groupedUser => new TotalProjectAmountStatistic() { Country = groupedUser.Key.Country, Total = groupedUser.Sum(item => item.Total), Date = groupedUser.Key.Date }).ToList();

    }
}

    public static class TotalProjectStatisticListExtension
{
    public static void MergeNullAndEmplyCountry(this IEnumerable<TotalProjectStatistic> totalUserStatistic)
    {
        foreach (var item in totalUserStatistic)
        {
            if (string.IsNullOrWhiteSpace(item.Country))
            {
                item.Country = "";
            }
        }

        totalUserStatistic = totalUserStatistic
                            .GroupBy(item => new { item.Country, item.Date })
                            .Select(groupedUser => new TotalProjectStatistic() { Country = groupedUser.Key.Country, Total = groupedUser.Sum(item => item.Total), Date = groupedUser.Key.Date }).ToList();

    }
}

    public static class TotalUserStatisticListExtension
{
    public static void MergeNullAndEmplyCountry(this IEnumerable<TotalUserStatistic> totalUserStatistic)
    {
        foreach (var item in totalUserStatistic)
        {
            if (string.IsNullOrWhiteSpace(item.Country))
            {
                item.Country = "";
            }
        }

        totalUserStatistic = totalUserStatistic
                            .GroupBy(item => new { item.Country, item.Date })
                            .Select(groupedUser => new TotalUserStatistic() { Country = groupedUser.Key.Country, Total = groupedUser.Sum(item => item.Total), Date = groupedUser.Key.Date }).ToList();

    }
}
\$\endgroup\$
4
\$\begingroup\$

You can use an interface and a generic method to remove the code duplication:

public interface IStatistic
{
    string Country {get; set;}
    DateTime Date {get; set;}
    Decimal Total {get; set;}
}

public static class StatisticExtension
{
    public static void MergeNullAndEmplyCountry<T>(this IEnumerable<T> totalUserStatistic) where T : IStatistic, new()
    {
        foreach (var item in totalUserStatistic)
            if (string.IsNullOrWhiteSpace(item.Country))
                item.Country = "";

        totalUserStatistic = totalUserStatistic.GroupBy(item => new { item.Country, item.Date })
                                                .Select(groupedUser => new T() 
                                                { 
                                                    Country = groupedUser.Key.Country, 
                                                    Total = groupedUser.Sum(item => item.Total), 
                                                    Date = groupedUser.Key.Date 
                                                }).ToList();
    }
}

Important here is the new() constraint, which allows us to create an instance of T.

If the Total property of a class is int instead of decimal, we can make use of an explicit interface implementation:

public class BlaStatistic : IStatistic
{
    public string Country {get; set;}
    public DateTime Date {get; set;}
    public Int32 Total {get; set;}
    Decimal IStatistic.Total 
    {   
        get { return Convert.ToDecimal(Total); }
        set { Total = Convert.ToInt32(value); }
    }
}

Also, your extension method should actually return something, because it won't change the IEnumerable<...> you pass in:

public static IEnumerable<T> MergeNullAndEmplyCountry<T>(this IEnumerable<T> totalUserStatistic) where T : IStatistic, new()
{
    ...
    return totalUserStatistic.GroupBy...
}
\$\endgroup\$
1
\$\begingroup\$

First of all, your code can't possibly work. All your methods do is to modify one of its parameters, which won't have any effect from the outside. So I'm going to assume your code actually does something useful, like returning the grouped collection.

Now, what you could do (if you don't have it already) is to create a generic interface IStatistic (or abstract base class) that will contain the three properties (Country, Date and generic Total):

public interface IStatistic<TTotal>
{
    string Country { get; set; }
    DateTime Date { get; set; }
    TTotal Total { get; set; }
}

Then you implement this interface in all your Statistic classes and write an extension method for this interface. The only problem will be with Total, because you can't call Sum() on just any type.

You can fix this by having two extension methods: one for int and one for decimal. These methods can share almost all of their code, by using a delegate that represents the right Sum() method for the given type.

Another problem is that if the extension methods for int and decimal had the same name, overload resolution wouldn't be able to choose between the two. Changing the name to include the type is not nice, but works:

public static class StatisticExtension
{
    // doesn't have to be public
    public static IEnumerable<TStatistic> MergeNullAndEmptyCountry<TStatistic, TTotal>(
        this IEnumerable<TStatistic> totalStatistic,
        Func<IEnumerable<TTotal>, TTotal> sumFunction)
        where TStatistic : class, IStatistic<TTotal>, new()
    {
        foreach (var item in totalStatistic)
        {
            if (String.IsNullOrWhiteSpace(item.Country))
            {
                item.Country = "";
            }
        }

        return totalStatistic
            .GroupBy(item => new { item.Country, item.Date })
            .Select(
                groupedUser =>
                new TStatistic
                {
                    Country = groupedUser.Key.Country,
                    Total = sumFunction(groupedUser.Select(item => item.Total)),
                    Date = groupedUser.Key.Date
                }).ToList();
    }

    public static IEnumerable<TStatistic> MergeNullAndEmptyCountryInt32<TStatistic>(
        this IEnumerable<TStatistic> totalStatistic)
        where TStatistic : class, IStatistic<int>, new()
    {
        return totalStatistic.MergeNullAndEmptyCountry<TStatistic, int>(Enumerable.Sum);
    }

    public static IEnumerable<TStatistic> MergeNullAndEmptyCountryDecimal<TStatistic>(
        this IEnumerable<TStatistic> totalStatistic)
        where TStatistic : class, IStatistic<decimal>, new()
    {
        return totalStatistic.MergeNullAndEmptyCountry<TStatistic, decimal>(Enumerable.Sum);
    }
}

Also, the name of the method is quite confusing (even ignoring the typo): it doesn't merge just null and empty countries, it merges everything.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.