我在考虑编写一些通用的函数来执行基本的数学运算,比如最小值、最大值等。但是我不知道如何比较两种通用类型:
public T Max<T>(T v1, T v2) where T: struct
{
return (v1 > v2 ? v1 : v2);
}
这个怎么样?
谢谢。
您可能希望限制泛型类型实现IComparable
:
public T Max<T>(T v1, T v2) where T: struct, IComparable<T>
然后使用CompareTo
方法:
{
return (v1.CompareTo(v2) > 0 ? v1 : v2);
}
struct
避免了 null
问题,但在我看来这是错误的答案;Luke 是正确的。 - Marc GravellT
的默认比较器。例如: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
接口,则会抛出运行时异常。Comparer<T>.Default
传递给第二个,它接受IComparer<T>
(用于那些想要自定义比较器的情况)。但对于简单的情况,这就是正确的方式。 - Marc GravellT:IComparable <T>
,则代码可以简化为return(x.CompareTo(y)> 0)...
。更易于阅读。并且没有运行时评估或异常风险,因为您已将T限制为可比较的类型。 - ToolmakerSteveComparer<T>.Default
需要在运行时检查 T 是否实现了 IComparable<T>
、IComparable
或两者都没有。虽然这种行为在http://msdn.microsoft.com/en-us/library/azhsac5f.aspx中没有得到很好的记录,但我们可以推断出来,因为当 T 既没有实现>也没有实现时,Comparer<T>.Default
会抛出一个异常。如果在编译时进行检查,就不需要异常(运行时),只需使用编译时错误即可。Comparer<T>.Default
使用了反射,这意味着运行时成本很高,那么...它不是通用的...为什么?public static int Max( int x, int y)
{
return (x.CompareTo(y) > 0) ? x : y;
}
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); });
}
//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); });
}
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); });
}
虽然有点晚,但为什么不使用动态类型和委托作为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;
}
static abstract
接口方法的加法。该功能引入了许多接口,允许对数字类型和/或数学运算进行通用抽象化,从而使得可以将相关函数重写为:public T Max<T>(T v1, T v2) where T: IComparisonOperators<T, T, bool> => v1 > v2 ? v1 : v2;
Max
,因为它已经在INumber<T>
接口中定义了:public interface INumber<TSelf> :
// ... other interfaces,
System.Numerics.IComparisonOperators<TSelf,TSelf,bool>
// ... other interfaces
where TSelf : INumber<TSelf>
我在这个答案中提出的解决方案曾经可行(我实际上做了类似的事情),在问题被提出时。我很惊讶没有答案提出这种替代方案,因此我会提出它。
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
来模仿 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
从记忆中来看,T 还需要是 IComparable
(将其添加到 where
中),然后您可以使用 v1.CompareTo(v2) > 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://learn.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://learn.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");
}
IComparable
接口的任何内容。 - JoeyIComparable[<T>]
)的代码。 - Marc Gravellnew[] {v1, v2}.Max()
怎么样? - Can Bud