如何将泛型类型与 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 类型时,此方法工作正常。但如果它是 longdouble 或几乎任何其他类型,它就会抛出异常。

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;
}