C#:泛型数学函数(Min,Max等)

时间:2009-12-15 10:34:00

标签: c# math

我正在考虑为Min,Max等基本数学运算编写通用函数。 但我不知道如何比较两种通用类型:

public T Max<T>(T v1, T v2) where T: struct
{
   return (v1 > v2 ? v1 : v2);
}

那怎么样?

谢谢。

7 个答案:

答案 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 MiscUtilrelated 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的实现,我们将反射添加到非常简单的东西。

简而言之:

  1. 总是更喜欢编译时错误到运行时异常(这不是Javascript,Ruby,... :-))
  2. 与手写版本(任何类型)相比,此实现效率较低
  3. 另一方面。 当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);
}

这不需要 dynamicComparison<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.MemoryBarrierInterlocked.Exchange o 模仿 Volatile.ReadVolatile.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