Henrik Clausen
Henrik Clausen

Reputation: 729

c# generic with supplied parameter names

I'm trying to create a generic function that can take a list of objects with start-datetime and end-datetime and combine them if they're right after one another with no gaps between.

public static IEnumerable<T> MakeBlocks<T>(IEnumerable<T> input)
{
    List<T> outList = new List<T>();
    if (input.Count() == 0) return outList;

    T thisEntry = input.First();
    foreach (var nextEntry in input.Skip(1))
    {
        if ( nextEntry != null && nextEntry.StartDT == thisEntry.EndDT)
        {
            thisEntry.EndDT = nextEntry.EndDT;
        }
        else
        {
            outList.Add(thisEntry);
            thisEntry = nextEntry;
        }
    }
    outList.Add(thisEntry);

    return outList;
}

This works fine if I know what the "From" and "to" property is called, but how can I do this with a generic?

The "unknown" properties in the above pseudo-example are called StartDT and EndDT, but they could be called anything.

In JavaScript I can just supply the the property name as a string, but that won't do in c#.

Is this possible and how?

Upvotes: 1

Views: 90

Answers (2)

Ann L.
Ann L.

Reputation: 13965

This is possible, if you pass Getter and Setter functions:

    public static IEnumerable<T> MakeBlocks<T>(IEnumerable<T> input, 
       Func<T, DateTime> getStartProperty, 
       Func<T, DateTime> getEndProperty, 
       Action<T, DateTime> setEndProperty) {

        List<T> outList = new List<T>();
        if (!input.Any()) return outList;
    
        T thisEntry = input.First();
        foreach (var nextEntry in input.Skip(1))
        {
            if ( nextEntry != null && getStartProperty(nextEntry) == getEndProperty(thisEntry))
            {
                setEndProperty(thisEntry, getEndProperty(nextEntry));
            }
            else
            {
                outList.Add(thisEntry);
                thisEntry = nextEntry;
            }
        }
        outList.Add(thisEntry);

        return outList;
        
    }

So you'd invoke it with some lambda functions:

var result = MakeBlocks<MyClass>(
    myCollection, 
    mc => mc.StartDate, 
    mc => mc.EndDate, 
    (mc, value) => mc.EndDate = value
);

You can also -- with more effort -- use Linq Expressions to provide just the two Getters, and use them to determine the property names and synthesize a Setter. But, more effort.

Upvotes: 2

Kent Kostelac
Kent Kostelac

Reputation: 2446

You can use generic constraints. So you would have a class that all your T's inherit from. Like so:

Modified method to use generic constarint

public static IEnumerable<T> MakeBlocks<T>(IEnumerable<T> input) where T : SomeClass
{
    List<T> outList = new List<T>();
    if (input.Count() == 0) return outList;

    T thisEntry = input.First();
    foreach (var nextEntry in input.Skip(1))
    {
        if (nextEntry != null && nextEntry.StartDT == thisEntry.EndDT)
        {
            thisEntry.EndDT = nextEntry.EndDT;
        }
        else
        {
            outList.Add(thisEntry);
            thisEntry = nextEntry;
        }
    }
    outList.Add(thisEntry);

    return outList;
}

Base class that all your T's should inherit from

public abstract class SomeClass
{
    public DateTime EndDT { get; set; }
    public DateTime StartDT { get; set; }
}

Upvotes: 3

Related Questions