可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I happened to have seen some code where this guy passed a lambda expression to a ArrayList.Sort(IComparer here) or a IEnumerable.SequenceEqual(IEnumerable list, IEqualityComparer here) where an IComparer or an IEqualityComparer was expected.
I can't be sure if I saw it though, or I am just dreaming. And I can't seem to find an extension on any of these collections that accepts a Func<> or a delegate in their method signatures.
Is there such an overload/extension method? Or, if not, is it possible to muck around like this and pass an algorithm (read delegate) where a single-method interface is expected?
Update
Thanks, everyone. That's what I thought. I must've been dreaming. I know how to write a conversion. I just wasn't sure if I'd seen something like that or just thought I'd seen it.
Yet another update
Look, here, I found one such instance. I wasn't dreaming after all. Look at what this guy is doing here. What gives?
And here's another update:
Ok, I get it. The guy's using the Comparison<T>
overload. Nice. Nice, but totally prone to mislead you. Nice, though. Thanks.
回答1:
I'm not much sure what useful it really is, as I think for most cases in the Base Library expecting an IComparer there's an overload that expects a Comparison... but just for the record:
in .Net 4.5 they've added a method to obtain an IComparer from a Comparison:
Comparer.Create
so you can pass your lambda to it and obtain an IComparer.
回答2:
I was also googling the web for a solution, but i didn't found any satisfying one. So i've created a generic EqualityComparerFactory:
public static class EqualityComparerFactory<T>
{
private class MyComparer : IEqualityComparer<T>
{
private readonly Func<T, int> _getHashCodeFunc;
private readonly Func<T, T, bool> _equalsFunc;
public MyComparer(Func<T, int> getHashCodeFunc, Func<T, T, bool> equalsFunc)
{
_getHashCodeFunc = getHashCodeFunc;
_equalsFunc = equalsFunc;
}
public bool Equals(T x, T y)
{
return _equalsFunc(x, y);
}
public int GetHashCode(T obj)
{
return _getHashCodeFunc(obj);
}
}
public static IEqualityComparer<T> CreateComparer(Func<T, int> getHashCodeFunc, Func<T, T, bool> equalsFunc)
{
if (getHashCodeFunc == null)
throw new ArgumentNullException("getHashCodeFunc");
if (equalsFunc == null)
throw new ArgumentNullException("equalsFunc");
return new MyComparer(getHashCodeFunc, equalsFunc);
}
}
The idea is, that the CreateComparer method takes two arguments: a delegate to GetHashCode(T) and a delegate to Equals(T,T)
Example:
class Person
{
public int Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
class Program
{
static void Main(string[] args)
{
var list1 = new List<Person>(new[]{
new Person { Id = 1, FirstName = "Walter", LastName = "White" },
new Person { Id = 2, FirstName = "Jesse", LastName = "Pinkman" },
new Person { Id = 3, FirstName = "Skyler", LastName = "White" },
new Person { Id = 4, FirstName = "Hank", LastName = "Schrader" },
});
var list2 = new List<Person>(new[]{
new Person { Id = 1, FirstName = "Walter", LastName = "White" },
new Person { Id = 4, FirstName = "Hank", LastName = "Schrader" },
});
// We're comparing based on the Id property
var comparer = EqualityComparerFactory<Person>.CreateComparer(a => a.Id.GetHashCode(), (a, b) => a.Id==b.Id);
var intersection = list1.Intersect(list2, comparer).ToList();
}
}
回答3:
You can provide a lambda for a Array.Sort method, as it requires a method that accepts two objects of type T and returns an integer. As such, you could provide a lambda of the following definition (a, b) => a.CompareTo(b)
. An example to do a descending sort of an integer array:
int[] array = { 1, 8, 19, 4 };
// descending sort
Array.Sort(array, (a, b) => -1 * a.CompareTo(b));
回答4:
public class Comparer2<T, TKey> : IComparer<T>, IEqualityComparer<T>
{
private readonly Expression<Func<T, TKey>> _KeyExpr;
private readonly Func<T, TKey> _CompiledFunc
// Constructor
public Comparer2(Expression<Func<T, TKey>> getKey)
{
_KeyExpr = getKey;
_CompiledFunc = _KeyExpr.Compile();
}
public int Compare(T obj1, T obj2)
{
return Comparer<TKey>.Default.Compare(_CompiledFunc(obj1), _CompiledFunc(obj2));
}
public bool Equals(T obj1, T obj2)
{
return EqualityComparer<TKey>.Default.Equals(_CompiledFunc(obj1), _CompiledFunc(obj2));
}
public int GetHashCode(T obj)
{
return EqualityComparer<TKey>.Default.GetHashCode(_CompiledFunc(obj));
}
}
use it like this
ArrayList.Sort(new Comparer2<Product, string>(p => p.Name));
回答5:
You can't pass it directly however you could do so by defining a LambdaComparer
class that excepts a Func<T,T,int>
and then uses that in it's CompareTo
.
It is not quite as concise but you could make it shorter through some creative extension methods on Func
.
回答6:
I vote for the dreaming theory.
You can't pass a function where an object is expected: derivatives of System.Delegate (which is what lambdas are) don't implement those interfaces.
What you probably saw is a use of the of the Converter<TInput, TOutput>
delegate, which can be modeled by a lambda. Array.ConvertAll uses an instance of this delegate.
回答7:
These methods don't have overloads that accept a delegate instead of an interface, but:
- You can normally return a simpler sort key through the delegate you pass to
Enumerable.OrderBy
- Likewise, you could call
Enumerable.Select
before calling Enumerable.SequenceEqual
- It should be straightforward to write a wrapper that implements
IEqualityComparer<T>
in terms of Func<T, T, bool>
- F# lets you implement this sort of interface in terms of a lambda :)
回答8:
In case if you need this function for use with lambda and possibly two different element types:
static class IEnumerableExtensions
{
public static bool SequenceEqual<T1, T2>(this IEnumerable<T1> first, IEnumerable<T2> second, Func<T1, T2, bool> comparer)
{
if (first == null)
throw new NullReferenceException("first");
if (second == null)
throw new NullReferenceException("second");
using (IEnumerator<T1> e1 = first.GetEnumerator())
using (IEnumerator<T2> e2 = second.GetEnumerator())
{
while (e1.MoveNext())
{
if (!(e2.MoveNext() && comparer(e1.Current, e2.Current)))
return false;
}
if (e2.MoveNext())
return false;
}
return true;
}
}