Question

I have the following code which I want to put into a function so I don't have to copy/paste it several times for each case of my if-block:

        if (sort == ExportSortOrder.CardType)
        {
            cards = cards.OrderBy(f => f.CardType);
            foreach (CardTypes type in Enum.GetValues(typeof(CardTypes)))
            {
                if (!cards.Any(f => f.CardType == type))
                    continue;
                builder.Append(type.ToString() + ": ");
                builder.Append(cards.Where(f => f.CardType== type).Sum(f => f.AmountInDeck));
                builder.Append(Environment.NewLine);
                foreach (CardViewModel card in cards.Where(f => f.CardType == type))
                {
                    builder.Append(card.AmountInDeck.ToString() + "\t" + card.Name.Name + Environment.NewLine);                    
                }
                builder.Append(Environment.NewLine);
            }
        }
        else if ( //same code block as above, for a different property of CardViewModel

The type T of the property to sort "cards" by is always an Enum. So far, I have the following method to call:

    private void AddSections<T>(IEnumerable<CardViewModel> cards, StringBuilder builder, Func<CardViewModel, T> order, Func<CardViewModel, bool> predicate)
    {
        cards = cards.OrderBy(order);
        foreach (T value in Enum.GetValues(typeof(T)))
        {
            if (!cards.Any(predicate))
                continue;
            builder.Append(value.ToString() + ": ");
            builder.Append(cards.Where(predicate).Sum(f => f.AmountInDeck));
            builder.Append(Environment.NewLine);

            foreach (CardViewModel card in cards.Where(predicate))
            {
                builder.Append(card.AmountInDeck.ToString() + "\t" + card.Name.Name + Environment.NewLine);
            }

            builder.Append(Environment.NewLine);
        }
    }

However, now I have the problem of how to build the last parameter of AddSections<T>(), the predicate by which to filter, because I need a different predicate for each run of the outer loop. I cannot pass it as a fixed parameter because I obviously can't reference "value" in the outer foreach loop. So I think I have to build it dynamically inside the AddSections function. If that is the case, how do I do that, or am I on the wrong track entirely?

Was it helpful?

Solution

The trick here is to replace Func<CardViewModel, bool> predicate with Func<T, Func<CardViewModel, bool>> predicateFactory.

I think this does the trick:

private void AddSections<T>(
    IEnumerable<CardViewModel> cards,
    StringBuilder builder,
    Func<CardViewModel, T> order,
    Func<T, Func<CardViewModel, bool>> predicateFactory)
{
    cards = cards.OrderBy(order);
    foreach (T value in Enum.GetValues(typeof(T)))
    {
        if (!cards.Any(predicateFactory(value)))
            continue;
        builder.Append(value.ToString() + ": ");
        builder.Append(cards
            .Where(predicateFactory(value))
            .Sum(f => f.AmountInDeck));
        builder.Append(Environment.NewLine);

        foreach (CardViewModel card in cards.Where(predicateFactory(value)))
        {
            builder.Append(card.AmountInDeck.ToString()
                + "\t"
                + card.Name.Name
                + Environment.NewLine);
        }

        builder.Append(Environment.NewLine);
    }
}

Your call to AddSections would look like this:

this.AddSections<CardTypes>(cards, builder,
    m => m.CardType,
    v => m => m.CardType == v);

Alternatively, you might be able to simply shortcut the need to pass a predicate (or predicate factory) at all. Since it appears you are just dealing with enums you could do this:

private void AddSections<T>(
    IEnumerable<CardViewModel> cards,
    StringBuilder builder,
    Func<CardViewModel, T> order)
{
    cards = cards.OrderBy(order);
    foreach (T value in Enum.GetValues(typeof(T)))
    {
        if (!cards.Any(c => order(c) == value))
            continue;
        builder.Append(value.ToString() + ": ");
        builder.Append(cards
            .Where(c => order(c) == value)
            .Sum(f => f.AmountInDeck));
        builder.Append(Environment.NewLine);

        foreach (CardViewModel card in cards.Where(c => order(c) == value))
        {
            builder.Append(card.AmountInDeck.ToString()
                + "\t"
                + card.Name.Name
                + Environment.NewLine);
        }

        builder.Append(Environment.NewLine);
    }
}

And then your call becomes this:

this.AddSections<CardTypes>(cards, builder, m => m.CardType);

OTHER TIPS

You can do it like this:

private void AddSections<T>(
    IEnumerable<CardViewModel> cards,
    StringBuilder builder,
    Func<CardViewModel, T> order,
    Func<CardViewModel, T, bool> prepredicate)
{
    cards = cards.OrderBy(order);
    foreach (T value in Enum.GetValues(typeof(T)))
    {
        Func<CardViewModel, bool> predicate = f => prepredicate(f,value);
        ...

AddSections<CardTypes>(cards, builder, order, (f,t) => f.CardType == t);

We give the function AddSection a function which can be use to build the predicate. Inside the loop we can then build the actual predicate by using a lambda.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top