przechodząc do rzeczy to tak - na pewno nie da się zrobić jakieś super narzędzia dla każdego, bo jak kluczy mamy 20 to pisać 20xByKey
albo jak klucze przychodzą "z zewnątrz" itp itd
Nie trzeba, można mieć jedną metodę, która przyjmie 20 kluczy jako string podanych "z zewnątrz". A oprócz tego można mieć API oparte na lambdach, w którym działa podpowiadanie składni, i które jest dzięki temu wygodne dla programisty.
- w cpp jest coś takiego jak varadic templates, z tego co wiem c# nie ma takiej dobroci
.ByKey(x => x.Prop1, x => x.Prop2) jak takie coś zbudować jak nie poprzez .ByKey<TProp1, TProp2>(x => x.Prop1, x => x.Prop2)
przy sporej kluczy to już zaczyna się robić problem i zostaje tylko ByKey<TProp> (chyba że czegoś nie wiem)
Nie sądzę, aby variadic templates były rozwiązaniem pomogło. Problem nie polega na zmiennej liczbie typów ale na zmiennej liczbie parametrów. Do tego służy słowo kluczowe params
.
to jest trochę takie zaskakujące w kodzie - niby nic, nie takie rzeczy się widziało, ale jak ktoś to zobaczy to się zdziwi czemu before(after) czemu [0] itd
W moim przykładzie chodziło mi o ogólną przejrzystość i sugestywne nazwy metod, nie o poprawność składniową.
Generalnie widzę opcje dwie:
- albo klasa porównująca z metodami konfigurującymi sposób porównania (na zasadzie buildera), a na końcu jawnym wywołaniem jakiejś metody "Run", która dokona porównania.
To wygląda tak:
public class CollectionComparer<T>
{
private IEnumerable<T> beforeCollection;
private IEnumerable<T> afterCollection;
public CollectionComparer(IEnumerable<T> beforeCollection, IEnumerable<T> afterCollection)
{
this.beforeCollection = beforeCollection;
this.afterCollection = afterCollection;
}
public static MemberInfo GetMemberInfo(LambdaExpression exp)
{
var body = exp.Body as MemberExpression;
return body.Member;
}
public CollectionComparer<T> ByKey(params Expression<Func<T, object>>[] keys)
{
// todo
return this;
}
public CollectionComparer<T> Exclude(params Expression<Func<T, object>>[] excludedProps)
{
// todo
return this;
}
public CollectionComparer<T> IgnoreFields()
{
// todo
return this;
}
internal CollectionComparer<T> ExcludeMembers(Func<string, bool> propertyNameCondition)
{
// todo
return this;
}
internal ComparisionResult Run()
{
// todo
return new ComparisionResult();
}
}
Użycie:
var result = new CollectionComparer<Person>(beforeCollection, afterCollection)
.ByKey(x => x.Name, x => x.LastName)
.Exclude(x => x.Age, x => x.City)
.ExcludeMembers(o => o.EndsWith("Date"))
.IgnoreFields()
.Run();
- albo metoda rozszerzająca
IEnumerable<T>
przyjmująca w parametrze drugą kolekcję oraz konfigurację porównania (też na zasadzie buildera).
public static class EnumerableExtensions
{
public static ComparisionResult CompareWith<T>(this IEnumerable<T> before, IEnumerable<T> after,
Func<ComparisionOptions<T>, ComparisionOptions<T>> options)
{
// todo
return new ComparisionResult();
}
}
public class ComparisionOptions<T>
{
public ComparisionOptions<T> ByKey(params Expression<Func<T, object>>[] keys)
{
// todo
return this;
}
public ComparisionOptions<T> Exclude(params Expression<Func<T, object>>[] excludedProps)
{
// todo
return this;
}
public ComparisionOptions<T> IgnoreFields()
{
// todo
return this;
}
public ComparisionOptions<T> ExcludeMembers(Expression<Func<string, bool>> propertyNameCondition)
{
// todo
return this;
}
}
Użycie:
var result2 = beforeCollection.CompareWith(afterCollection, options =>
options.ByKey(x => x.Name, x => x.LastName)
.Exclude(x => x.Age, x => x.City)
.ExcludeMembers(o => o.EndsWith("Date"))
.IgnoreFields()
);
Konkludując
budowa API to nie jest taka prosta sprawa, tu niby nieskoplikowany projekcik, ot taka se porównywarka, a jak zrobić aby zaspokajała potrrzeby wynikające z różnych możliwych przypadków użycia to już nie takie oczywiste (istnieje możliwość, że tak myślę bo brakuje mi obycia w temacie)
Trudno się nie zgodzić. :)
1 w C# widziałem głownie dwa rozwiązania. Jedno stosowane nawet przez microsoft, tak wiemy że język ma ograniczenia, dlatego dodaliśmy 16 przeciązeń z coraz wiekszą liczbą parametów
Zgaduję, że masz na myśli konstruktory od Func
i Action
, ale tam problemem jest liczba parametrów generycznych.