谁能告诉我泛型是否有办法将泛型类型参数限制为T
:
Int16
Int32
Int64
UInt16
UInt32
UInt64
我知道where
关键字,但找不到仅适用于这些类型的接口,
就像是:
static bool IntegerFunction<T>(T value) where T : INumeric
谁能告诉我泛型是否有办法将泛型类型参数限制为T
:
Int16
Int32
Int64
UInt16
UInt32
UInt64
我知道where
关键字,但找不到仅适用于这些类型的接口,
就像是:
static bool IntegerFunction<T>(T value) where T : INumeric
C# 不支持这一点。Hejlsberg在接受 Bruce Eckel 的采访时描述了不实施该功能的原因:
而且还不清楚增加的复杂性是否值得您获得的少量收益。如果约束系统不直接支持你想做的事情,你可以用工厂模式来做。例如,您可能有一个
Matrix<T>
,并且Matrix
您想在其中定义一个点积方法。这当然意味着你最终需要了解如何将两个T
s 相乘,但你不能说这是一个约束,至少不是 ifT
isint
,double
, orfloat
。但是你可以做的是将你的Matrix
取值作为一个参数Calculator<T>
,并且在Calculator<T>
其中有一个名为 的方法multiply
。你去实现它并将它传递给Matrix
.
然而,这会导致相当复杂的代码,用户必须为他们想要使用的每个提供自己的Calculator<T>
实现。T
只要它不必是可扩展的,即如果您只想支持固定数量的类型,例如int
and double
,您可以使用相对简单的接口:
var mat = new Matrix<int>(w, h);
但是,一旦您希望用户能够提供他们自己的自定义类型,您就需要打开这个实现,以便用户可以提供他们自己的Calculator
实例。例如,要实例化一个使用自定义十进制浮点实现的矩阵DFP
,您必须编写以下代码:
var mat = new Matrix<DFP>(DfpCalculator.Instance, w, h);
…并实施所有成员DfpCalculator : ICalculator<DFP>
。
正如 Sergey Shandar 的回答中所讨论的,另一种方法(不幸的是具有相同的限制)是使用策略类。
考虑到这个问题的受欢迎程度以及这种功能背后的兴趣,我惊讶地发现还没有涉及 T4 的答案。
在这个示例代码中,我将演示一个非常简单的示例,说明如何使用强大的模板引擎来完成编译器在后台使用泛型所做的工作。
您可以简单地为您喜欢的每种类型生成所需的函数并相应地使用它(在编译时!),而不是通过箍和牺牲编译时确定性。
为此:
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<# Type[] types = new[] {
typeof(Int16), typeof(Int32), typeof(Int64),
typeof(UInt16), typeof(UInt32), typeof(UInt64)
};
#>
using System;
public static class MaxMath {
<# foreach (var type in types) {
#>
public static <#= type.Name #> Max (<#= type.Name #> val1, <#= type.Name #> val2) {
return val1 > val2 ? val1 : val2;
}
<#
} #>
}
就是这样。你现在完成了。
保存这个文件会自动编译成这个源文件:
using System;
public static class MaxMath {
public static Int16 Max (Int16 val1, Int16 val2) {
return val1 > val2 ? val1 : val2;
}
public static Int32 Max (Int32 val1, Int32 val2) {
return val1 > val2 ? val1 : val2;
}
public static Int64 Max (Int64 val1, Int64 val2) {
return val1 > val2 ? val1 : val2;
}
public static UInt16 Max (UInt16 val1, UInt16 val2) {
return val1 > val2 ? val1 : val2;
}
public static UInt32 Max (UInt32 val1, UInt32 val2) {
return val1 > val2 ? val1 : val2;
}
public static UInt64 Max (UInt64 val1, UInt64 val2) {
return val1 > val2 ? val1 : val2;
}
}
在您的main
方法中,您可以验证您是否具有编译时确定性:
namespace TTTTTest
{
class Program
{
static void Main(string[] args)
{
long val1 = 5L;
long val2 = 10L;
Console.WriteLine(MaxMath.Max(val1, val2));
Console.Read();
}
}
}
我先说一句:不,这不违反 DRY 原则。DRY 原则是为了防止人们在多个地方复制代码,这会导致应用程序变得难以维护。
这里根本不是这种情况:如果您想要更改,那么您只需更改模板(您这一代人的单一来源!)就可以了。
为了将它与您自己的自定义定义一起使用,请在生成的代码中添加一个命名空间声明(确保它与您将定义自己的实现的命名空间声明相同)并将该类标记为partial
. 然后,将这些行添加到您的模板文件中,以便将其包含在最终编译中:
<#@ import namespace="TheNameSpaceYouWillUse" #>
<#@ assembly name="$(TargetPath)" #>
老实说:这很酷。
免责声明:此示例深受Manning Publications 的 Kevin Hazzard 和 Jason Bock 的 .NET 元编程的影响。
对此没有任何限制。对于任何想要使用泛型进行数值计算的人来说,这都是一个真正的问题。
我会更进一步说我们需要
static bool GenericFunction<T>(T value)
where T : operators( +, -, /, * )
甚至
static bool GenericFunction<T>(T value)
where T : Add, Subtract
不幸的是,你只有接口、基类和关键字struct
(必须是值类型)、class
(必须是引用类型)和new()
(必须有默认构造函数)
您可以将数字包装在 codeproject 上的其他东西(类似于INullable<T>
)中。
您可以在运行时应用限制(通过反映运算符或检查类型),但这确实失去了首先拥有泛型的优势。
使用策略的解决方法:
interface INumericPolicy<T>
{
T Zero();
T Add(T a, T b);
// add more functions here, such as multiplication etc.
}
struct NumericPolicies:
INumericPolicy<int>,
INumericPolicy<long>
// add more INumericPolicy<> for different numeric types.
{
int INumericPolicy<int>.Zero() { return 0; }
long INumericPolicy<long>.Zero() { return 0; }
int INumericPolicy<int>.Add(int a, int b) { return a + b; }
long INumericPolicy<long>.Add(long a, long b) { return a + b; }
// implement all functions from INumericPolicy<> interfaces.
public static NumericPolicies Instance = new NumericPolicies();
}
算法:
static class Algorithms
{
public static T Sum<P, T>(this P p, params T[] a)
where P: INumericPolicy<T>
{
var r = p.Zero();
foreach(var i in a)
{
r = p.Add(r, i);
}
return r;
}
}
用法:
int i = NumericPolicies.Instance.Sum(1, 2, 3, 4, 5);
long l = NumericPolicies.Instance.Sum(1L, 2, 3, 4, 5);
NumericPolicies.Instance.Sum("www", "") // compile-time error.
该解决方案是编译时安全的。CityLizard Framework为 .NET 4.0 提供编译版本。该文件是 lib/NETFramework4.0/CityLizard.Policy.dll。
它也可以在 Nuget 中使用:https ://www.nuget.org/packages/CityLizard/ 。请参阅CityLizard.Policy.I结构。
从 C# 7.3 开始,您可以使用更接近的近似值-非托管约束来指定类型参数是非指针、不可为空的非托管类型。
class SomeGeneric<T> where T : unmanaged
{
//...
}
非托管约束意味着 struct 约束,不能与 struct 或 new() 约束结合使用。
如果类型是以下任何类型,则该类型是非托管类型:
要进一步限制和消除不实现 IComparable 的指针和用户定义类型添加IComparable(但枚举仍然是从 IComparable 派生的,因此通过添加 IEquatable<T> 来限制枚举,您可以根据自己的情况更进一步并添加额外的接口。 unmanaged 允许保持这个列表更短):
class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
{
//...
}
但这并不妨碍 DateTime 实例化。
这个问题有点像常见问题解答,所以我将其发布为 wiki(因为我之前发布过类似的问题,但这是一个较旧的问题);反正...
您使用的是哪个版本的 .NET?如果您使用的是 .NET 3.5,那么我在MiscUtil中有一个通用运算符实现(免费等)。
这具有类似 的方法T Add<T>(T x, T y)
,以及用于不同类型(如DateTime + TimeSpan
)的算术的其他变体。
此外,这适用于所有内置、提升和定制的运算符,并缓存委托以提高性能。
这里有一些关于为什么这很棘手的额外背景。
您可能还想知道dynamic
(4.0) 也间接解决了这个问题 - 即
dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect
不幸的是,在这种情况下,您只能在 where 子句中指定 struct。不能具体指定 Int16、Int32 等似乎很奇怪,但我确信在 where 子句中不允许值类型的决定背后有一些深层的实现原因。
我想唯一的解决方案是进行运行时检查,不幸的是可以防止在编译时发现问题。那会是这样的: -
static bool IntegerFunction<T>(T value) where T : struct {
if (typeof(T) != typeof(Int16) &&
typeof(T) != typeof(Int32) &&
typeof(T) != typeof(Int64) &&
typeof(T) != typeof(UInt16) &&
typeof(T) != typeof(UInt32) &&
typeof(T) != typeof(UInt64)) {
throw new ArgumentException(
string.Format("Type '{0}' is not valid.", typeof(T).ToString()));
}
// Rest of code...
}
我知道这有点难看,但至少提供了所需的约束。
我还会研究此实现可能对性能产生的影响,也许有更快的方法。
可能你能做的最接近的是
static bool IntegerFunction<T>(T value) where T: struct
不确定您是否可以执行以下操作
static bool IntegerFunction<T>(T value) where T: struct, IComparable
, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
对于如此具体的事情,为什么不只是为每种类型都重载,列表是如此之短,它可能会占用更少的内存。
主题很旧,但对于未来的读者:
Discriminated Unions
此功能与迄今为止在 C# 中未实现的功能密切相关。我在这里发现了它的问题:
https://github.com/dotnet/csharplang/issues/113
这个问题仍然是开放的,功能已经计划好了C# 10
所以我们仍然需要再等一会儿,但是在发布之后你可以这样做:
static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...
是的,或者说很快就会有!
查看此.NET 博客文章。
从 .NET 6(我认为是预览版 7)开始,您将能够使用以下接口INumber
并IFloatingPoint
创建以下程序:
using System;
Console.WriteLine(Sum(1, 2, 3, 4, 5));
Console.WriteLine(Sum(10.541, 2.645));
Console.WriteLine(Sum(1.55f, 5, 9.41f, 7));
static T Sum<T>(params T[] numbers) where T : INumber<T>
{
T result = T.Zero;
foreach (T item in numbers)
{
result += item;
}
return result;
}
INumber
目前来自System.Runtime.Experimental
NuGet 包。我上面示例的项目文件看起来像
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.Experimental" Version="6.0.0-preview.7.21377.19" />
</ItemGroup>
</Project>
还有诸如IAdditionOperators
和之类的接口,IComparisonOperators
因此您可以通用地使用特定的运算符。
无法将模板限制为类型,但您可以根据类型定义不同的操作。作为通用数值包的一部分,我需要一个通用类来添加两个值。
class Something<TCell>
{
internal static TCell Sum(TCell first, TCell second)
{
if (typeof(TCell) == typeof(int))
return (TCell)((object)(((int)((object)first)) + ((int)((object)second))));
if (typeof(TCell) == typeof(double))
return (TCell)((object)(((double)((object)first)) + ((double)((object)second))));
return second;
}
}
请注意,typeofs 是在编译时评估的,因此编译器将删除 if 语句。编译器还删除了虚假的强制转换。所以有些东西会在编译器中解析为
internal static int Sum(int first, int second)
{
return first + second;
}
我创建了一个小库功能来解决这些问题:
代替:
public T DifficultCalculation<T>(T a, T b)
{
T result = a * b + a; // <== WILL NOT COMPILE!
return result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Should result in 8.
你可以写:
public T DifficultCalculation<T>(Number<T> a, Number<T> b)
{
Number<T> result = a * b + a;
return (T)result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Results in 8.
我有类似的情况,我需要处理数字类型和字符串;看起来有点奇怪,但你去。
同样,像许多人一样,我查看了约束并提出了一堆它必须支持的接口。但是,a) 它不是 100% 防水的,b) 任何新看这个长长的约束列表的人都会立即感到非常困惑。
所以,我的方法是将我所有的逻辑放入一个没有约束的泛型方法中,但将该泛型方法设为私有。然后我用公共方法公开它,一个明确处理我想要处理的类型 - 在我看来,代码是干净和明确的,例如
public static string DoSomething(this int input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this decimal input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this double input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this string input, ...) => DoSomethingHelper(input, ...);
private static string DoSomethingHelper<T>(this T input, ....)
{
// complex logic
}
我想知道和samjudson一样,为什么只对整数?如果是这种情况,您可能需要创建一个辅助类或类似的东西来保存您想要的所有类型。
如果您想要的只是整数,请不要使用泛型,那不是泛型;或者更好的是,通过检查其类型来拒绝任何其他类型。
目前还没有“好的”解决方案。但是,您可以显着缩小类型参数,以排除您假设的“INumeric”约束的许多不匹配,如 Haacked 上面所示。
static bool IntegerFunction<T>(T value) where T: IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>, struct {...
如果您使用的是 .NET 4.0 及更高版本,那么您可以只使用动态作为方法参数并在运行时检查传递的动态参数类型是数字/整数类型。
如果传递的动态类型不是数字/整数类型,则抛出异常。
实现该想法的示例短代码如下所示:
using System;
public class InvalidArgumentException : Exception
{
public InvalidArgumentException(string message) : base(message) {}
}
public class InvalidArgumentTypeException : InvalidArgumentException
{
public InvalidArgumentTypeException(string message) : base(message) {}
}
public class ArgumentTypeNotIntegerException : InvalidArgumentTypeException
{
public ArgumentTypeNotIntegerException(string message) : base(message) {}
}
public static class Program
{
private static bool IntegerFunction(dynamic n)
{
if (n.GetType() != typeof(Int16) &&
n.GetType() != typeof(Int32) &&
n.GetType() != typeof(Int64) &&
n.GetType() != typeof(UInt16) &&
n.GetType() != typeof(UInt32) &&
n.GetType() != typeof(UInt64))
throw new ArgumentTypeNotIntegerException("argument type is not integer type");
//code that implements IntegerFunction goes here
}
private static void Main()
{
Console.WriteLine("{0}",IntegerFunction(0)); //Compiles, no run time error and first line of output buffer is either "True" or "False" depends on the code that implements "Program.IntegerFunction" static method.
Console.WriteLine("{0}",IntegerFunction("string")); //Also compiles but it is run time error and exception of type "ArgumentTypeNotIntegerException" is thrown here.
Console.WriteLine("This is the last Console.WriteLine output"); //Never reached and executed due the run time error and the exception thrown on the second line of Program.Main static method.
}
当然,该解决方案仅在运行时有效,但在编译时无效。
如果您想要一个始终在编译时工作而不是在运行时工作的解决方案,那么您必须使用公共结构/类来包装动态,其重载的公共构造函数仅接受所需类型的参数并为结构/类提供适当的名称。
有意义的是,包装的动态始终是类/结构的私有成员,它是结构/类的唯一成员,结构/类的唯一成员的名称是“值”。
如有必要,您还必须为类/结构的私有动态成员定义和实现与所需类型一起使用的公共方法和/或运算符。
同样有意义的是,结构/类具有特殊/唯一的构造函数,该构造函数接受动态作为参数,初始化它只是称为“值”的私有动态成员,但此构造函数的修饰符当然是私有的。
一旦类/结构准备好,将 IntegerFunction 的参数类型定义为已定义的类/结构。
实现该想法的示例长代码如下所示:
using System;
public struct Integer
{
private dynamic value;
private Integer(dynamic n) { this.value = n; }
public Integer(Int16 n) { this.value = n; }
public Integer(Int32 n) { this.value = n; }
public Integer(Int64 n) { this.value = n; }
public Integer(UInt16 n) { this.value = n; }
public Integer(UInt32 n) { this.value = n; }
public Integer(UInt64 n) { this.value = n; }
public Integer(Integer n) { this.value = n.value; }
public static implicit operator Int16(Integer n) { return n.value; }
public static implicit operator Int32(Integer n) { return n.value; }
public static implicit operator Int64(Integer n) { return n.value; }
public static implicit operator UInt16(Integer n) { return n.value; }
public static implicit operator UInt32(Integer n) { return n.value; }
public static implicit operator UInt64(Integer n) { return n.value; }
public static Integer operator +(Integer x, Int16 y) { return new Integer(x.value + y); }
public static Integer operator +(Integer x, Int32 y) { return new Integer(x.value + y); }
public static Integer operator +(Integer x, Int64 y) { return new Integer(x.value + y); }
public static Integer operator +(Integer x, UInt16 y) { return new Integer(x.value + y); }
public static Integer operator +(Integer x, UInt32 y) { return new Integer(x.value + y); }
public static Integer operator +(Integer x, UInt64 y) { return new Integer(x.value + y); }
public static Integer operator -(Integer x, Int16 y) { return new Integer(x.value - y); }
public static Integer operator -(Integer x, Int32 y) { return new Integer(x.value - y); }
public static Integer operator -(Integer x, Int64 y) { return new Integer(x.value - y); }
public static Integer operator -(Integer x, UInt16 y) { return new Integer(x.value - y); }
public static Integer operator -(Integer x, UInt32 y) { return new Integer(x.value - y); }
public static Integer operator -(Integer x, UInt64 y) { return new Integer(x.value - y); }
public static Integer operator *(Integer x, Int16 y) { return new Integer(x.value * y); }
public static Integer operator *(Integer x, Int32 y) { return new Integer(x.value * y); }
public static Integer operator *(Integer x, Int64 y) { return new Integer(x.value * y); }
public static Integer operator *(Integer x, UInt16 y) { return new Integer(x.value * y); }
public static Integer operator *(Integer x, UInt32 y) { return new Integer(x.value * y); }
public static Integer operator *(Integer x, UInt64 y) { return new Integer(x.value * y); }
public static Integer operator /(Integer x, Int16 y) { return new Integer(x.value / y); }
public static Integer operator /(Integer x, Int32 y) { return new Integer(x.value / y); }
public static Integer operator /(Integer x, Int64 y) { return new Integer(x.value / y); }
public static Integer operator /(Integer x, UInt16 y) { return new Integer(x.value / y); }
public static Integer operator /(Integer x, UInt32 y) { return new Integer(x.value / y); }
public static Integer operator /(Integer x, UInt64 y) { return new Integer(x.value / y); }
public static Integer operator %(Integer x, Int16 y) { return new Integer(x.value % y); }
public static Integer operator %(Integer x, Int32 y) { return new Integer(x.value % y); }
public static Integer operator %(Integer x, Int64 y) { return new Integer(x.value % y); }
public static Integer operator %(Integer x, UInt16 y) { return new Integer(x.value % y); }
public static Integer operator %(Integer x, UInt32 y) { return new Integer(x.value % y); }
public static Integer operator %(Integer x, UInt64 y) { return new Integer(x.value % y); }
public static Integer operator +(Integer x, Integer y) { return new Integer(x.value + y.value); }
public static Integer operator -(Integer x, Integer y) { return new Integer(x.value - y.value); }
public static Integer operator *(Integer x, Integer y) { return new Integer(x.value * y.value); }
public static Integer operator /(Integer x, Integer y) { return new Integer(x.value / y.value); }
public static Integer operator %(Integer x, Integer y) { return new Integer(x.value % y.value); }
public static bool operator ==(Integer x, Int16 y) { return x.value == y; }
public static bool operator !=(Integer x, Int16 y) { return x.value != y; }
public static bool operator ==(Integer x, Int32 y) { return x.value == y; }
public static bool operator !=(Integer x, Int32 y) { return x.value != y; }
public static bool operator ==(Integer x, Int64 y) { return x.value == y; }
public static bool operator !=(Integer x, Int64 y) { return x.value != y; }
public static bool operator ==(Integer x, UInt16 y) { return x.value == y; }
public static bool operator !=(Integer x, UInt16 y) { return x.value != y; }
public static bool operator ==(Integer x, UInt32 y) { return x.value == y; }
public static bool operator !=(Integer x, UInt32 y) { return x.value != y; }
public static bool operator ==(Integer x, UInt64 y) { return x.value == y; }
public static bool operator !=(Integer x, UInt64 y) { return x.value != y; }
public static bool operator ==(Integer x, Integer y) { return x.value == y.value; }
public static bool operator !=(Integer x, Integer y) { return x.value != y.value; }
public override bool Equals(object obj) { return this == (Integer)obj; }
public override int GetHashCode() { return this.value.GetHashCode(); }
public override string ToString() { return this.value.ToString(); }
public static bool operator >(Integer x, Int16 y) { return x.value > y; }
public static bool operator <(Integer x, Int16 y) { return x.value < y; }
public static bool operator >(Integer x, Int32 y) { return x.value > y; }
public static bool operator <(Integer x, Int32 y) { return x.value < y; }
public static bool operator >(Integer x, Int64 y) { return x.value > y; }
public static bool operator <(Integer x, Int64 y) { return x.value < y; }
public static bool operator >(Integer x, UInt16 y) { return x.value > y; }
public static bool operator <(Integer x, UInt16 y) { return x.value < y; }
public static bool operator >(Integer x, UInt32 y) { return x.value > y; }
public static bool operator <(Integer x, UInt32 y) { return x.value < y; }
public static bool operator >(Integer x, UInt64 y) { return x.value > y; }
public static bool operator <(Integer x, UInt64 y) { return x.value < y; }
public static bool operator >(Integer x, Integer y) { return x.value > y.value; }
public static bool operator <(Integer x, Integer y) { return x.value < y.value; }
public static bool operator >=(Integer x, Int16 y) { return x.value >= y; }
public static bool operator <=(Integer x, Int16 y) { return x.value <= y; }
public static bool operator >=(Integer x, Int32 y) { return x.value >= y; }
public static bool operator <=(Integer x, Int32 y) { return x.value <= y; }
public static bool operator >=(Integer x, Int64 y) { return x.value >= y; }
public static bool operator <=(Integer x, Int64 y) { return x.value <= y; }
public static bool operator >=(Integer x, UInt16 y) { return x.value >= y; }
public static bool operator <=(Integer x, UInt16 y) { return x.value <= y; }
public static bool operator >=(Integer x, UInt32 y) { return x.value >= y; }
public static bool operator <=(Integer x, UInt32 y) { return x.value <= y; }
public static bool operator >=(Integer x, UInt64 y) { return x.value >= y; }
public static bool operator <=(Integer x, UInt64 y) { return x.value <= y; }
public static bool operator >=(Integer x, Integer y) { return x.value >= y.value; }
public static bool operator <=(Integer x, Integer y) { return x.value <= y.value; }
public static Integer operator +(Int16 x, Integer y) { return new Integer(x + y.value); }
public static Integer operator +(Int32 x, Integer y) { return new Integer(x + y.value); }
public static Integer operator +(Int64 x, Integer y) { return new Integer(x + y.value); }
public static Integer operator +(UInt16 x, Integer y) { return new Integer(x + y.value); }
public static Integer operator +(UInt32 x, Integer y) { return new Integer(x + y.value); }
public static Integer operator +(UInt64 x, Integer y) { return new Integer(x + y.value); }
public static Integer operator -(Int16 x, Integer y) { return new Integer(x - y.value); }
public static Integer operator -(Int32 x, Integer y) { return new Integer(x - y.value); }
public static Integer operator -(Int64 x, Integer y) { return new Integer(x - y.value); }
public static Integer operator -(UInt16 x, Integer y) { return new Integer(x - y.value); }
public static Integer operator -(UInt32 x, Integer y) { return new Integer(x - y.value); }
public static Integer operator -(UInt64 x, Integer y) { return new Integer(x - y.value); }
public static Integer operator *(Int16 x, Integer y) { return new Integer(x * y.value); }
public static Integer operator *(Int32 x, Integer y) { return new Integer(x * y.value); }
public static Integer operator *(Int64 x, Integer y) { return new Integer(x * y.value); }
public static Integer operator *(UInt16 x, Integer y) { return new Integer(x * y.value); }
public static Integer operator *(UInt32 x, Integer y) { return new Integer(x * y.value); }
public static Integer operator *(UInt64 x, Integer y) { return new Integer(x * y.value); }
public static Integer operator /(Int16 x, Integer y) { return new Integer(x / y.value); }
public static Integer operator /(Int32 x, Integer y) { return new Integer(x / y.value); }
public static Integer operator /(Int64 x, Integer y) { return new Integer(x / y.value); }
public static Integer operator /(UInt16 x, Integer y) { return new Integer(x / y.value); }
public static Integer operator /(UInt32 x, Integer y) { return new Integer(x / y.value); }
public static Integer operator /(UInt64 x, Integer y) { return new Integer(x / y.value); }
public static Integer operator %(Int16 x, Integer y) { return new Integer(x % y.value); }
public static Integer operator %(Int32 x, Integer y) { return new Integer(x % y.value); }
public static Integer operator %(Int64 x, Integer y) { return new Integer(x % y.value); }
public static Integer operator %(UInt16 x, Integer y) { return new Integer(x % y.value); }
public static Integer operator %(UInt32 x, Integer y) { return new Integer(x % y.value); }
public static Integer operator %(UInt64 x, Integer y) { return new Integer(x % y.value); }
public static bool operator ==(Int16 x, Integer y) { return x == y.value; }
public static bool operator !=(Int16 x, Integer y) { return x != y.value; }
public static bool operator ==(Int32 x, Integer y) { return x == y.value; }
public static bool operator !=(Int32 x, Integer y) { return x != y.value; }
public static bool operator ==(Int64 x, Integer y) { return x == y.value; }
public static bool operator !=(Int64 x, Integer y) { return x != y.value; }
public static bool operator ==(UInt16 x, Integer y) { return x == y.value; }
public static bool operator !=(UInt16 x, Integer y) { return x != y.value; }
public static bool operator ==(UInt32 x, Integer y) { return x == y.value; }
public static bool operator !=(UInt32 x, Integer y) { return x != y.value; }
public static bool operator ==(UInt64 x, Integer y) { return x == y.value; }
public static bool operator !=(UInt64 x, Integer y) { return x != y.value; }
public static bool operator >(Int16 x, Integer y) { return x > y.value; }
public static bool operator <(Int16 x, Integer y) { return x < y.value; }
public static bool operator >(Int32 x, Integer y) { return x > y.value; }
public static bool operator <(Int32 x, Integer y) { return x < y.value; }
public static bool operator >(Int64 x, Integer y) { return x > y.value; }
public static bool operator <(Int64 x, Integer y) { return x < y.value; }
public static bool operator >(UInt16 x, Integer y) { return x > y.value; }
public static bool operator <(UInt16 x, Integer y) { return x < y.value; }
public static bool operator >(UInt32 x, Integer y) { return x > y.value; }
public static bool operator <(UInt32 x, Integer y) { return x < y.value; }
public static bool operator >(UInt64 x, Integer y) { return x > y.value; }
public static bool operator <(UInt64 x, Integer y) { return x < y.value; }
public static bool operator >=(Int16 x, Integer y) { return x >= y.value; }
public static bool operator <=(Int16 x, Integer y) { return x <= y.value; }
public static bool operator >=(Int32 x, Integer y) { return x >= y.value; }
public static bool operator <=(Int32 x, Integer y) { return x <= y.value; }
public static bool operator >=(Int64 x, Integer y) { return x >= y.value; }
public static bool operator <=(Int64 x, Integer y) { return x <= y.value; }
public static bool operator >=(UInt16 x, Integer y) { return x >= y.value; }
public static bool operator <=(UInt16 x, Integer y) { return x <= y.value; }
public static bool operator >=(UInt32 x, Integer y) { return x >= y.value; }
public static bool operator <=(UInt32 x, Integer y) { return x <= y.value; }
public static bool operator >=(UInt64 x, Integer y) { return x >= y.value; }
public static bool operator <=(UInt64 x, Integer y) { return x <= y.value; }
}
public static class Program
{
private static bool IntegerFunction(Integer n)
{
//code that implements IntegerFunction goes here
//note that there is NO code that checks the type of n in rum time, because it is NOT needed anymore
}
private static void Main()
{
Console.WriteLine("{0}",IntegerFunction(0)); //compile error: there is no overloaded METHOD for objects of type "int" and no implicit conversion from any object, including "int", to "Integer" is known.
Console.WriteLine("{0}",IntegerFunction(new Integer(0))); //both compiles and no run time error
Console.WriteLine("{0}",IntegerFunction("string")); //compile error: there is no overloaded METHOD for objects of type "string" and no implicit conversion from any object, including "string", to "Integer" is known.
Console.WriteLine("{0}",IntegerFunction(new Integer("string"))); //compile error: there is no overloaded CONSTRUCTOR for objects of type "string"
}
}
请注意,为了在您的代码中使用动态,您必须添加对Microsoft.CSharp的引用
如果 .NET 框架的版本低于/低于/小于 4.0 并且该版本中未定义动态,那么您将不得不使用object并强制转换为整数类型,这很麻烦,所以我建议您使用 at如果可以的话,至少 .NET 4.0 或更高版本,这样您就可以使用dynamic而不是object。
不幸的是,.NET 并没有提供一种方法来实现这一点。
为了解决这个问题,我创建了 OSS 库Genumerics,它为以下内置数字类型及其可为空的等效项提供大多数标准数字操作,并能够添加对其他数字类型的支持。
sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, float
, double
, decimal
, 和BigInteger
性能相当于数字类型特定的解决方案,允许您创建高效的通用数字算法。
这是代码使用的示例。
public static T Sum(T[] items)
{
T sum = Number.Zero<T>();
foreach (T item in items)
{
sum = Number.Add(sum, item);
}
return sum;
}
public static T SumAlt(T[] items)
{
// implicit conversion to Number<T>
Number<T> sum = Number.Zero<T>();
foreach (T item in items)
{
// operator support
sum += item;
}
// implicit conversion to T
return sum;
}
练习的重点是什么?
正如人们已经指出的那样,您可以使用一个非泛型函数来获取最大的项目,编译器会自动为您转换较小的整数。
static bool IntegerFunction(Int64 value) { }
如果您的函数在性能关键路径上(极不可能,IMO),您可以为所有需要的函数提供重载。
static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }
我会使用一个通用的,你可以处理外部...
/// <summary>
/// Generic object copy of the same type
/// </summary>
/// <typeparam name="T">The type of object to copy</typeparam>
/// <param name="ObjectSource">The source object to copy</param>
public T CopyObject<T>(T ObjectSource)
{
T NewObject = System.Activator.CreateInstance<T>();
foreach (PropertyInfo p in ObjectSource.GetType().GetProperties())
NewObject.GetType().GetProperty(p.Name).SetValue(NewObject, p.GetValue(ObjectSource, null), null);
return NewObject;
}
当我尝试为泛型类型重载运算符时,这个限制影响了我。由于没有“INumeric”约束,并且由于一系列其他原因,stackoverflow 上的好人很乐意提供,因此无法在泛型类型上定义操作。
我想要类似的东西
public struct Foo<T>
{
public T Value{ get; private set; }
public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
{
return new Foo<T> { Value = LHS.Value + RHS.Value; };
}
}
我已经使用 .net4 动态运行时类型解决了这个问题。
public struct Foo<T>
{
public T Value { get; private set; }
public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
{
return new Foo<T> { Value = LHS.Value + (dynamic)RHS.Value };
}
}
关于使用的两件事dynamic
是
.NET 数字基元类型不共享任何允许它们用于计算的通用接口。可以定义自己的接口(例如ISignedWholeNumber
)来执行此类操作,定义包含单个Int16
,Int32
等的结构并实现这些接口,然后具有接受泛型类型的方法ISignedWholeNumber
,但必须转换数值对您的结构类型可能会很麻烦。
另一种方法是定义Int64Converter<T>
具有静态属性bool Available {get;};
和静态委托的静态类Int64 GetInt64(T value)
, T FromInt64(Int64 value)
, bool TryStoreInt64(Int64 value, ref T dest)
。类构造函数可以使用硬编码来加载已知类型的委托,并可能使用反射来测试类型是否T
实现了具有正确名称和签名的方法(如果它类似于包含一个Int64
和表示一个数字的结构,但有自定义ToString()
方法)。这种方法将失去与编译时类型检查相关的优势,但仍将设法避免装箱操作,并且每种类型只需“检查”一次。之后,与该类型关联的操作将替换为委托调度。
如果您只想使用一种数字类型,您可以考虑在 C++ 中使用using
.
因此,与其拥有非常通用的
T ComputeSomething<T>(T value1, T value2) where T : INumeric { ... }
你可以有
using MyNumType = System.Double;
T ComputeSomething<MyNumType>(MyNumType value1, MyNumType value2) { ... }
如果需要,这可能使您可以轻松地从double
toint
或其他人转到,但您将无法在同一程序中ComputeSomething
使用double
和使用。int
但是为什么不全部替换double
呢int
?因为您的方法可能希望使用 adouble
无论输入是double
还是int
。别名使您可以准确地知道哪个变量使用动态类型。
所有数字类型都是实现IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable
. 然而,也是如此DateTime
。
所以这个通用的扩展方法是可能的:
public static bool IsNumeric<T>(this T value) where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable =>
typeof(T) != typeof(DateTime);
但是对于实现这些接口的结构,它将失败,例如:
public struct Foo : IComparable, IComparable<Foo>, IConvertible, IEquatable<Foo>, IFormattable { /* ... */ }
这种非通用替代方案的性能较低,但可以保证工作:
public static bool IsNumeric(this Type type) =>
type == typeof(sbyte) || type == typeof(byte) ||
type == typeof(short) || type == typeof(ushort) ||
type == typeof(int) || type == typeof(uint) ||
type == typeof(long) || type == typeof(ulong) ||
type == typeof(float) ||
type == typeof(double) ||
type == typeof(decimal);
.NET 6 具有此功能的预览功能:
https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/#generic-math
文章中的一个例子:
static T Add<T>(T left, T right)
where T : INumber<T>
{
return left + right;
}
INumber
是实现其他接口的接口,例如IAdditionOperators
允许泛型+
使用的接口。这现在是可能的,因为另一个预览特性是接口中的静态抽象,因为+
运算符重载是一个静态方法:
/// <summary>Defines a mechanism for computing the sum of two values.</summary>
/// <typeparam name="TSelf">The type that implements this interface.</typeparam>
/// <typeparam name="TOther">The type that will be added to <typeparamref name="TSelf" />.</typeparam>
/// <typeparam name="TResult">The type that contains the sum of <typeparamref name="TSelf" /> and <typeparamref name="TOther" />.</typeparam>
[RequiresPreviewFeatures(Number.PreviewFeatureMessage, Url = Number.PreviewFeatureUrl)]
public interface IAdditionOperators<TSelf, TOther, TResult>
where TSelf : IAdditionOperators<TSelf, TOther, TResult>
{
/// <summary>Adds two values together to compute their sum.</summary>
/// <param name="left">The value to which <paramref name="right" /> is added.</param>
/// <param name="right">The value which is added to <paramref name="left" />.</param>
/// <returns>The sum of <paramref name="left" /> and <paramref name="right" />.</returns>
static abstract TResult operator +(TSelf left, TOther right);
}