我正在考虑为Min,Max等基本数学运算编写通用函数。 但我不知道如何比较两种通用类型:
public T Max<T>(T v1, T v2) where T: struct
{
return (v1 > v2 ? v1 : v2);
}
那怎么样?
谢谢。
答案 0 :(得分:22)
您可能希望constrain通用类型实现IComparable
:
public T Max<T>(T v1, T v2) where T: struct, IComparable<T>
然后使用CompareTo
方法:
{
return (v1.CompareTo(v2) > 0 ? v1 : v2);
}
答案 1 :(得分:22)
如果您只想创建比较函数,则可以使用default comparer作为T
类型。例如:
public static T Max<T>(T x, T y)
{
return (Comparer<T>.Default.Compare(x, y) > 0) ? x : y;
}
如果T
实施IComparable<T>
,那么将使用该比较器;如果T
未实现IComparable<T>
但实施IComparable
,则会使用该比较器;如果T
未实现IComparable<T>
或IComparable
,则会抛出运行时异常。
如果您想要/需要做的不仅仅是比较项目,那么您可以查看generic operators implementation in MiscUtil和related article。
答案 2 :(得分:4)
让我不同意。 @ LukeH的实现不是Generic 。
我将解释为什么它不是Generic:
Comparer<T>.Default
涉及在运行时检查T以确定它是否实现IComparable<T>
,IComparable
或两者都没有。
虽然http://msdn.microsoft.com/en-us/library/azhsac5f.aspx中没有详细记录此行为,但我们可以推断它,因为{T}没有实现时Comparer<T>.Default
抛出异常。如果检查是在编译时完成的,则不需要异常(运行时),编译时错误就足够了。
然后,由于Comparer<T>.Default
使用反射,这意味着运行时的成本很高,然后....,它不是通用的 ...为什么?
因为通用编程意味着:单个算法(Generic)可以涵盖许多实现(对于许多类型),保持手写版本的效率。
举个例子。整数的手写版本将是:
public static int Max( int x, int y)
{
return (x.CompareTo(y) > 0) ? x : y;
}
这很简单,只涉及比较(或者更多,取决于如何实现Int32.CompareTo())。 如果我们使用@LukeH的实现,我们将反射添加到非常简单的东西。
简而言之:
另一方面。 当x和y等价时,您认为Max应该返回什么?
我开始分析Real-Generic实现....
理想的实现方式就像......
public static T Max<T>(T x, T y, Func<T, T, int> cmp)
{
return (cmp(x, y) > 0) ? x : y;
}
//Pseudo-code ( note the 'or' next to 'where' )
public static T Max<T>(T x, T y) where T: IComparable<T> or IComparable
{
return Max(x, y, (a, b) => { return a.CompareTo(b); });
}
这在C#中是不可能的,下一次尝试可能是......
//pseudo-code
public static T Max<T>(T x, T y, Func<T, T, int> cmp)
{
return (cmp(x, y) > 0) ? x : y;
}
public static T Max<T>(T x, T y) where T: IComparable<T>
{
return Max(x, y, (a, b) => { return a.CompareTo(b); });
}
public static T Max<T>(T x, T y) where T: IComparable
{
return Max(x, y, (a, b) => { return a.CompareTo(b); });
}
但是,这既不可能,因为重载决议没有考虑泛型约束......
然后,我会有意识地遗漏IComparable
。我只是担心IComparable<T>
public static T Max<T>(T x, T y, Func<T, T, int> cmp)
{
return (cmp(x, y) > 0) ? x : y;
}
public static T Max<T>(T x, T y) where T: IComparable<T>
{
return Max(x, y, (a, b) => { return a.CompareTo(b); });
}
答案 3 :(得分:3)
这有点太晚了,但为什么不使用动态类型和委托作为IComparable的替代?这样,在大多数情况下,您将获得编译类型的安全性,并且只有当提供的类型都不支持运算符时才会出现运行时错误。并且您未能提供默认比较器作为参数。
public static T Max<T>(T first, T second, Func<T,T,bool> f = null)
{
Func<dynamic,dynamic,bool> is_left_smaller = (x, y) => x < y ? true : false;
var compare = f ?? new Func<T, T, bool>((x, y) => is_left_smaller(x, y));
return compare(first, second) ? second : first;
}
答案 4 :(得分:0)
从内存中,T也需要IComparable
(将其添加到where
),然后使用v1.CompareTo(v2) > 0
等。
答案 5 :(得分:0)
不断创新。 您可以对任意数量的参数使用最大/最小函数:
public static T Min<T>(params T[] source)
where T: struct, IComparable<T>
{
if (source == null)
throw new System.ArgumentNullException("source");
T value = default(T);
bool hasValue = false;
foreach (T x in source)
{
if (hasValue)
{
// if (x < value) // https://docs.microsoft.com/en-us/dotnet/api/system.icomparable-1?view=netcore-3.1
// Less than zero This object precedes the object specified by the CompareTo method in the sort order.
// Zero This current instance occurs in the same position in the sort order as the object specified by the CompareTo method argument.
// Greater than zero
if (x.CompareTo(value) < 0)
value = x;
}
else
{
value = x;
hasValue = true;
}
}
if (hasValue)
return value;
throw new System.InvalidOperationException("Sequence contains no elements");
}
public static T Max<T>(params T[] source)
where T : struct, IComparable<T>
{
if (source == null)
throw new System.ArgumentNullException("source");
T value = default(T);
bool hasValue = false;
foreach (T x in source)
{
if (hasValue)
{
// if (x > value) // https://docs.microsoft.com/en-us/dotnet/api/system.icomparable-1?view=netcore-3.1
// Less than zero This object precedes the object specified by the CompareTo method in the sort order.
// Zero This current instance occurs in the same position in the sort order as the object specified by the CompareTo method argument.
// Greater than zero
if (x.CompareTo(value) > 0)
value = x;
}
else
{
value = x;
hasValue = true;
}
}
if (hasValue)
return value;
throw new System.InvalidOperationException("Sequence contains no elements");
}
答案 6 :(得分:0)
我在这个答案中提出的解决方案在提出问题时会起作用(我实际上做了类似的事情)。我很惊讶没有答案提供这种替代方案,因此我将介绍它。
您可以(并且当时可以使用)Linq.Expressions
(2007 年在 .NET 3.5 中添加,使其成为问题时的有效答案)。
对于初学者:
using System.Linq.Expressions;
// ...
public T Max<T>(T v1, T v2)
{
var expression = Expression.GreaterThan
(
Expression.Constant(v1),
Expression.Constant(v2)
);
return Expression.Lambda<Func<bool>>(expression).Compile()() ? v1 : v2);
}
这不需要 dynamic
或 Comparison<T>
/IComparer<T>
。
我相信有一种方法来指定自定义比较会更好,但这不是我们在这里要做的。当然,对于所提供的解决方案适用的任何类型,Comparer<T>.Default
都可以使用。但是,使用 Linq.Expressions
将允许您实现任何算术运算。 以此为例说明该方法。
当然有开销。让我们有一个编译成带参数函数的版本,我们可以稍后考虑如何缓存它:
using System.Linq.Expressions;
// ...
public T Max<T>(T v1, T v2)
{
var a = Expression.Parameter(typeof(int), "a");
var b = Expression.Parameter(typeof(int), "b");
var lambda = Expression.Lambda<Func<T, T, bool>>
(
Expression.GreaterThan(a, b),
new[]{a, b}
);
return ((Func<T, T, bool>)lambda.Compile())(v1, v2) ? v1 : v2;
}
好的,为了缓存它,让我们从泛型类方法开始,它更容易编写:
using System.Linq.Expressions;
class GenericMath<T>
{
private static Func<T, T, bool>? _greaterThan;
public static Func<T, T, bool> GetGreaterThan()
{
if (_greaterThan == null)
{
var a = Expression.Parameter(typeof(int), "a");
var b = Expression.Parameter(typeof(int), "b");
var lambda = Expression.Lambda<Func<T, T, bool>>
(
Expression.GreaterThan(a, b),
new[]{a, b}
);
_greaterThan = (Func<T, T, bool>)lambda.Compile();
}
return _greaterThan;
}
public static T Max(T v1, T v2)
{
return GetGreaterThan()(v1, v2) ? v1 : v2;
}
}
当然,泛型上的静态有缺点(有性能成本,而且永远不会释放内存)。我们可以通过使用字典缓存来开始寻找更好的解决方案:
using System.Linq.Expressions;
class GenericMath
{
private readonly static Dictionary<Type, Delegate> _gtCache = new Dictionary<Type, Delegate>();
public static Func<T, T, bool> GetGreaterThan<T>()
{
if (!_gtCache.TryGetValue(typeof(T), out var @delegate) || @delegate == null)
{
var a = Expression.Parameter(typeof(int), "a");
var b = Expression.Parameter(typeof(int), "b");
var lambda = Expression.Lambda<Func<T, T, bool>>
(
Expression.GreaterThan(a, b),
new[]{a, b}
);
@delegate = lambda.Compile();
_addCache[typeof(T)] = @delegate;
}
return (Func<T, T, bool>)@delegate;
}
public static T Max<T>(T v1, T v2)
{
return GetGreaterThan<T>()(v1, v2) ? v1 : v2;
}
}
好的,我听到了,我引入了一个新问题:该解决方案不是线程安全的。
我们可以使用 ConcurrentDictionary
(在 .NET 4.0 中添加,如果我没记错的话,在提出问题时它处于测试阶段),但无论如何我们都不会释放内存。相反,我们可以为此用途创建一个自定义类:
public sealed class TypeCacheDict<TValue>
{
private const int Capacity = 256;
private readonly Entry[] _entries;
public TypeCacheDict()
{
_entries = new Entry[Capacity];
}
public TValue this[Type key]
{
get
{
if (TryGetValue(key, out var value))
{
return value;
}
throw new KeyNotFoundException();
}
set => Add(key, value);
}
public void Add(Type key, TValue value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
var hash = key.GetHashCode();
var index = hash & (_entries.Length - 1);
var entry = _entries[index];
Thread.MemoryBarrier();
if (entry?.Hash != hash || !entry.Key.Equals(key))
{
Interlocked.Exchange(ref _entries[index], new Entry(hash, key, value));
}
}
public bool TryGetValue(Type key, out TValue value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
var hash = key.GetHashCode();
var index = hash & (_entries.Length - 1);
var entry = _entries[index];
Thread.MemoryBarrier();
if (entry?.Hash == hash && entry.Key.Equals(key))
{
value = entry.Value;
return value != null;
}
value = default;
return false;
}
private sealed class Entry
{
internal readonly int Hash;
internal readonly Type Key;
internal readonly TValue Value;
internal Entry(int hash, Type key, TValue value)
{
Hash = hash;
Key = key;
Value = value;
}
}
}
这个 TypeCacheDict
是线程安全的。首先,Entry
是不可变的。我们无需担心对其的共享访问。另外,它是一个引用类型,所以替换它是一个原子操作。我们正在使用 Thread.MemoryBarrier
和 Interlocked.Exchange
o 模仿 Volatile.Read
和 Volatile.Write
因为 Volatile
不可用(并且 Thread.Volatile*
缺乏通用重载,我会而不是引入额外的演员表)。
有了这个新类型,我们现在可以写:
private readonly static TypeCacheDict<Delegate> _gtCache = new TypeCacheDict<Delegate>();
其余的代码可以保持不变。虽然还有改进的空间:TryGetOrAdd
:
public TValue TryGetOrAdd(Type key, Func<TValue> valueFactory)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (valueFactory == null)
{
throw new ArgumentNullException(nameof(valueFactory));
}
var hash = key.GetHashCode();
var index = hash & (_entries.Length - 1);
var entry = _entries[index];
Thread.MemoryBarrier();
if (entry?.Hash == hash && entry.Key.Equals(key))
{
return entry.Value;
}
var value = valueFactory();
Interlocked.Exchange(ref _entries[index], new Entry(hash, key, value));
return value;
}
允许我们写:
public static Func<T, T, bool> GetGreaterThan<T>()
{
return (Func<T, T, bool>)_gtCache.TryGetOrAdd
(
typeof(T),
()=>
{
var a = Expression.Parameter(typeof(int), "a");
var b = Expression.Parameter(typeof(int), "b");
var lambda = Expression.Lambda<Func<T, T, bool>>(Expression.GreaterThan(a, b), new[]{a, b});
return lambda.Compile();
}
);
}
当然,这就是你如何使用它:
Console.WriteLine(GenericMath.Max<int>(90, 100)); // 100
为了展示这种方法的威力,这是Add
:
private readonly static TypeCacheDict<Delegate> _addCache = new TypeCacheDict<Delegate>();
public static Func<T, T, T> GetAdd<T>()
{
return (Func<T, T, T>)_addCache.TryGetOrAdd
(
typeof(T),
()=>
{
var a = Expression.Parameter(typeof(int), "a");
var b = Expression.Parameter(typeof(int), "b");
var lambda = Expression.Lambda<Func<T, T, T>>(Expression.Add(a,b), new[]{a, b});
return lambda.Compile();
}
);
}
public static T Add<T>(T v1, T v2)
{
return GetAdd<T>()(v1, v2);
}
这就是你如何使用它:
Console.WriteLine(GenericMath.Add<int>(90, 100)); // 190