如何将泛型类型与 1 进行比较
How to compare generic type to 1
以下扩展方法returns仅当value
不等于1时s
的复数形式
public static string Pluralize<T>(this string s, T value) where T : struct, IComparable
{
return (value.CompareTo(1) == 0) ? s : s.Pluralize();
}
当值为 int
类型时,此方法工作正常。但如果它是 long
、double
或几乎任何其他类型,它就会抛出异常。
System.ArgumentException: 'Object must be of type Double.'
有谁知道将 value
与 1 进行比较的通用方法,即使 T
不是 int
?
否则,我将为每个支持的类型创建一个重载。
使用泛型时,您将放弃处理值的大部分能力。泛型约束回馈了其中的一部分,但不幸的是,仍然没有办法将类型约束为整数类型,即使现在许多 c# 版本都在讨论这一点。
如果您可以保证该方法只会被可转换为整数的类型调用,您可以使用 Convert.ToInt32(value)
对其进行转换,以便比较结果。但是对于 non-numeric 类型,这将在运行时失败。
您还可以对要处理的每种类型使用类型检查:
switch (value)
{
case float fValue:
return fValue == 1f ? s : s.Pluralize();
...
default:
return "fail";
}
这会是更多的代码,但可以让您处理默认情况而无需求助于捕获异常。
您可以应用 2 种解决方案(如果您想冒险使用预览功能,则可以应用 3 种)。
使用 dynamic
:
dynamic v = value;
return v == 1 ? s : s.Pluralize();
由于可能的基元数量已经穷尽,您也可以简单地使用开关或模式匹配,如下所示:
return value switch
{
1 or 1L or 1UL or 1.0d or 1.0f or 1.0m => s, // covers int, long, ulong, double, float and decimal
uint x when x == 1 => s,
short x when x == 1 => s,
ushort x when x == 1 => s,
byte x when x == 1 => s,
sbyte x when x == 1 => s,
char x when x == 1 => s,
_ => s.Pluralize()
};
(不幸的是,uint、(u)short、(s)byte 和 char 没有文字表示...)
但是,您可以利用 C#/.Net 路线图上的新“Generic Math”功能。不幸的是,它没有适合 .NET 6,但您已经可以尝试了(预览版)。这将允许您按如下方式实现通用功能:
static string Pluralize<T>(this string s, INumber<T> value) where T : INumber<T>
=> value.Equals(T.One) ? s : s.Pluralize();
要使其正常工作,您需要将其添加到您的 csproj 文件中:
<PropertyGroup>
<LangVersion>preview</LangVersion>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.Experimental" Version="6.0.2-mauipre.1.22054.8" />
</ItemGroup>
您可以将泛型约束设置为 IComparable、IComparable、IEquatable 并比较两个泛型值。值可以是 class 或结构,因为它实现了接口。 Int、String、Guid,一切正常。
public bool CompareGenericValues<T>(T value1, T value2)
where T : IComparable, IComparable<T>, IEquatable<T>
{
return value1.Equals(value2);
}
我们有一个类似的用例,基本上需要这些测试输入:
[TestMethod]
public void Test_Pluralize()
{
var testString = "Unpluralized";
// Integer
Console.WriteLine(testString.Pluralize(0));
Console.WriteLine(testString.Pluralize(1));
// String
Console.WriteLine(testString.Pluralize("0"));
Console.WriteLine(testString.Pluralize("1"));
// Double
Console.WriteLine(testString.Pluralize(0.12));
Console.WriteLine(testString.Pluralize(1.23));
// Long (but will still throw an exception if > Int32.MaxValue)
Console.WriteLine(testString.Pluralize((Int64)Int32.MaxValue));
// IConvertibleToInt Class (Custom interface)
Console.WriteLine(testString.Pluralize(new ConvertibleToIntTest(1)));
Console.WriteLine(testString.Pluralize(new ConvertibleToIntTest(2)));
// IConvertible Class (Standard interface)
Console.WriteLine(testString.Pluralize(new ConvertibleTest(1)));
Console.WriteLine(testString.Pluralize(new ConvertibleTest(1000)));
// Invalid class T
Console.WriteLine(testString.Pluralize(new NotConvertibleTest()));
}
到return这个:
所以我们为任何 T 写了这个扩展:
public static class Extensions
{
public static string Pluralize<T>(this string s, T value)
{
int iValue;
var type = typeof(T).Name;
if(value is IConvertible conv)
{
iValue = (int)Convert.ChangeType(conv, typeof(int));
}
else if (value is IConvertibleToInt conv2int)
{
iValue = conv2int.ToInt();
}
else
{
return $"[{type}] Requires IConvertible or IConvertibleToInt";
}
switch (iValue.CompareTo(1))
{
case 0: // Meaning that iValue is 1
return (value is double dbl) ?
$"{type}({dbl}) {s}":
$"{type}({iValue}) {s}";
default: // Any other value that is not 1
return Pluralize(s);
}
string Pluralize(string str)
{
return (value is double dbl) ?
$"{type}({dbl}) Pluralized" :
$"{type}({iValue}) Pluralized";
}
}
}
泛型 class T 可以选择实现 IConvertible(标准接口)或自定义接口,如下例所示:
public interface IConvertibleToInt
{
int ToInt();
}
public class ConvertibleToIntTest : IConvertibleToInt
{
public ConvertibleToIntTest(int value) => IntegerValue = value;
public int IntegerValue { get; set; }
public int ToInt() => IntegerValue;
}
以下扩展方法returns仅当value
不等于1时s
的复数形式
public static string Pluralize<T>(this string s, T value) where T : struct, IComparable
{
return (value.CompareTo(1) == 0) ? s : s.Pluralize();
}
当值为 int
类型时,此方法工作正常。但如果它是 long
、double
或几乎任何其他类型,它就会抛出异常。
System.ArgumentException: 'Object must be of type Double.'
有谁知道将 value
与 1 进行比较的通用方法,即使 T
不是 int
?
否则,我将为每个支持的类型创建一个重载。
使用泛型时,您将放弃处理值的大部分能力。泛型约束回馈了其中的一部分,但不幸的是,仍然没有办法将类型约束为整数类型,即使现在许多 c# 版本都在讨论这一点。
如果您可以保证该方法只会被可转换为整数的类型调用,您可以使用 Convert.ToInt32(value)
对其进行转换,以便比较结果。但是对于 non-numeric 类型,这将在运行时失败。
您还可以对要处理的每种类型使用类型检查:
switch (value)
{
case float fValue:
return fValue == 1f ? s : s.Pluralize();
...
default:
return "fail";
}
这会是更多的代码,但可以让您处理默认情况而无需求助于捕获异常。
您可以应用 2 种解决方案(如果您想冒险使用预览功能,则可以应用 3 种)。
使用 dynamic
:
dynamic v = value;
return v == 1 ? s : s.Pluralize();
由于可能的基元数量已经穷尽,您也可以简单地使用开关或模式匹配,如下所示:
return value switch
{
1 or 1L or 1UL or 1.0d or 1.0f or 1.0m => s, // covers int, long, ulong, double, float and decimal
uint x when x == 1 => s,
short x when x == 1 => s,
ushort x when x == 1 => s,
byte x when x == 1 => s,
sbyte x when x == 1 => s,
char x when x == 1 => s,
_ => s.Pluralize()
};
(不幸的是,uint、(u)short、(s)byte 和 char 没有文字表示...)
但是,您可以利用 C#/.Net 路线图上的新“Generic Math”功能。不幸的是,它没有适合 .NET 6,但您已经可以尝试了(预览版)。这将允许您按如下方式实现通用功能:
static string Pluralize<T>(this string s, INumber<T> value) where T : INumber<T>
=> value.Equals(T.One) ? s : s.Pluralize();
要使其正常工作,您需要将其添加到您的 csproj 文件中:
<PropertyGroup>
<LangVersion>preview</LangVersion>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.Experimental" Version="6.0.2-mauipre.1.22054.8" />
</ItemGroup>
您可以将泛型约束设置为 IComparable、IComparable、IEquatable 并比较两个泛型值。值可以是 class 或结构,因为它实现了接口。 Int、String、Guid,一切正常。
public bool CompareGenericValues<T>(T value1, T value2)
where T : IComparable, IComparable<T>, IEquatable<T>
{
return value1.Equals(value2);
}
我们有一个类似的用例,基本上需要这些测试输入:
[TestMethod]
public void Test_Pluralize()
{
var testString = "Unpluralized";
// Integer
Console.WriteLine(testString.Pluralize(0));
Console.WriteLine(testString.Pluralize(1));
// String
Console.WriteLine(testString.Pluralize("0"));
Console.WriteLine(testString.Pluralize("1"));
// Double
Console.WriteLine(testString.Pluralize(0.12));
Console.WriteLine(testString.Pluralize(1.23));
// Long (but will still throw an exception if > Int32.MaxValue)
Console.WriteLine(testString.Pluralize((Int64)Int32.MaxValue));
// IConvertibleToInt Class (Custom interface)
Console.WriteLine(testString.Pluralize(new ConvertibleToIntTest(1)));
Console.WriteLine(testString.Pluralize(new ConvertibleToIntTest(2)));
// IConvertible Class (Standard interface)
Console.WriteLine(testString.Pluralize(new ConvertibleTest(1)));
Console.WriteLine(testString.Pluralize(new ConvertibleTest(1000)));
// Invalid class T
Console.WriteLine(testString.Pluralize(new NotConvertibleTest()));
}
到return这个:
所以我们为任何 T 写了这个扩展:
public static class Extensions
{
public static string Pluralize<T>(this string s, T value)
{
int iValue;
var type = typeof(T).Name;
if(value is IConvertible conv)
{
iValue = (int)Convert.ChangeType(conv, typeof(int));
}
else if (value is IConvertibleToInt conv2int)
{
iValue = conv2int.ToInt();
}
else
{
return $"[{type}] Requires IConvertible or IConvertibleToInt";
}
switch (iValue.CompareTo(1))
{
case 0: // Meaning that iValue is 1
return (value is double dbl) ?
$"{type}({dbl}) {s}":
$"{type}({iValue}) {s}";
default: // Any other value that is not 1
return Pluralize(s);
}
string Pluralize(string str)
{
return (value is double dbl) ?
$"{type}({dbl}) Pluralized" :
$"{type}({iValue}) Pluralized";
}
}
}
泛型 class T 可以选择实现 IConvertible(标准接口)或自定义接口,如下例所示:
public interface IConvertibleToInt
{
int ToInt();
}
public class ConvertibleToIntTest : IConvertibleToInt
{
public ConvertibleToIntTest(int value) => IntegerValue = value;
public int IntegerValue { get; set; }
public int ToInt() => IntegerValue;
}