views:

201

answers:

4

In my project I have the following three interfaces, which are implemented by classes that manage merging of a variety of business objects that have different structures.

public interface IMerger<TSource, TDestination>
{
    TDestination Merge(TSource source, TDestination destination);
}

public interface ITwoWayMerger<TSource1, TSource2, TDestination>
{
    TDestination Merge(TSource1 source1, TSource2 source2, TDestination destination);
}

public interface IThreeWayMerger<TSource1, TSource2, TSource3, TDestination>
{
    TDestination Merge(TSource1 source1, TSource2 source2, TSource3 source3, TDestination destination);
}

This works well, but I would rather have one IMerger interface which specifies a variable number of TSource parameters, something like this (example below uses params; I know this is not valid C#):

public interface IMerger<params TSources, TDestination>
{
    TDestination Merge(params TSource sources, TDestination destination);
}

It there any way to achieve this, or something functionally equivalent?

+6  A: 

You can't. That is a key part of the API. You could, however, do something around the side, such as accepting a Type[] argument. You might also think up some exotic "fluent API / extension method" way of doing it, but to be honest it probably won't be worth it; but something like:

obj.Merge<FirstType>(firstData).Merge<SecondType>(secondData)
     .Merge<ThirdType>(thirdData).Execute<TDestination>(dest);

or with generic type inference:

obj.Merge(firstData).Merge(secondData).Merge(thirdData).Execute(dest);

Each merge step would simple store away the work to do, only accessed by Execute.

Marc Gravell
Also, such an approach could indicate a rational "ordering" to the merging, which isn't true in our scenario.
Richard Ev
But then, so does "`TSource1 source1, TSource2 source2, TSource3 source3`" from the question...
Marc Gravell
My thinking is that source1 .. sourcen indicates "these are the sources", whereas a chain of .Merge calls indicates that the merges are happening in that specific order.
Richard Ev
Then use different names...
Marc Gravell
+1  A: 

The params can only be at the end or argument lists and is syntactic sugar for an array:

public interface IMerger<TSources, TDestination>
{
  TDestination Merge(TDestination destination, params TSource[] sources);
}

If you want to allow any type to be used, just use object[] instead of TSource.

Note: MS had this "problem" also when they did the Expression stuff. They came up with a bunch of delegates Action<> and Func<> with different numbers of generic arguments, but each delegate is another type in fact.

Lucero
My example using params is not actually valid C# since you can't use params in this context, have updated my post to make this clearer
Richard Ev
+1  A: 

The params keyword is only used in a method signature, it's not something that you can decorate a type with. So, the type is still just TSources, and you have to place the parameter decorated with params last in the method signature:

public interface IMerger<TSources, TDestination> {
    TDestination Merge(TDestination destination, params TSources[] sources);
}
Guffa
+2  A: 

It depends on whether you want your objects to be able to merge objects of different types or not.

For a homogeneous merge, all you need is this:

public interface IMerger<TSource, TDestination> {
    TDestination Merge(IEnumerable<TSource> sources, TDestination destination);
}

For a heterogeneous merge, consider requiring all source types to derive from a common base type:

public interface IMerger<TSourceBase, TDestination> {
    TDestination Merge(IEnumerable<TSourceBase> sources, TDestination destination);
}

I don't see any need for a param array, just pass in the collection of objects.

Christian Hayter