包含值类型和字符串的 C# 通用约束

Posted

技术标签:

【中文标题】包含值类型和字符串的 C# 通用约束【英文标题】:C# Generic constraints to include value types AND strings 【发布时间】:2012-02-03 10:49:32 【问题描述】:

我正在尝试在 IEnumerable 上编写一个仅适用于值类型和字符串的扩展方法。

public static string MyMethod<T>(this IEnumerable<T> source) where T : struct, string

然而,'string' 不是一个有效的约束,因为它是一个密封的类。

有什么办法吗?

编辑:

我实际上要做的是为动态构造的 SQL 中的“IN”子句准备一个值列表。

我有很多要清理的代码实例,例如以下代码:

sb.AppendLine(string.Format("AND value IN (0)", string.Join(",", Values.Select(x => x.ToSQL()).ToArray())));

ToSQL() 有处理 SqlInjection 的代码。

【问题讨论】:

对于您的实现,是什么让值类型和字符串可以接受而其他人不接受? 【参考方案1】:

也许您可以限制为 IConvertible 类型?可以使用这些接口方法转换的所有系统原语也实现了该接口,因此此限制要求 T 为以下之一:

布尔值 字节 字符 日期时间 十进制 双 Int(16、32 和 64 位) SByte 单(浮动) 字符串 UInt(16、32 和 64 位)

如果您有 IConvertible,很有可能它是这些类型中的一种,因为 IConvertible 接口实现起来非常麻烦,很少用于第三方类型。

主要缺点是,如果不实际将 T 转换为其中一种类型的实例,您的所有方法都知道如何调用 Object 和 IConvertible 方法,或者采用 Object 或 IConvertible 的方法。如果您需要更多的东西(比如使用 + 添加和/或连接的能力),我认为简单地设置两种方法,一种是通用的结构类型,另一种是强类型的字符串,总体上是最好的选择。

【讨论】:

好主意!我没想到。 太棒了! 此列表不包含 Nullable 类型( @EvgeniyMiroshnichenko 这不应该;该问题要求包含值类型和字符串的类型。值类型的 Nullable 不是值类型,也不是字符串,因此它们不是答案的一部分。【参考方案2】:

您需要定义 2 个单独的方法:

public static string MyMethod<T>(this IEnumerable<T> source) where T : struct
public static string MyMethod(this IEnumerable<string> source)

【讨论】:

您还可以使用第三个私有方法,这两种方法都调用该方法以使事情稍微干燥。有关类似问题,请参阅 this answer。 虽然“你不能”的答案更正确,但这个答案更有用。【参考方案3】:

不,你不能。通用约束总是“AND”-ed,如果你明白我的意思(即 所有 约束必须被满足),所以即使你试图使用一些未密封的类,这仍然会失败。

您为什么要这样做?也许还有另一种方法会更好。

【讨论】:

谢谢。什么是最好的选择?两种不同的方法? @Poz:鉴于我一开始不会将值格式化为 SQL,我建议重构以使用参数化查询... 我们最初试图走这条路。然而,在 SQL Server 中将列表作为参数传递的问题,以及对值中可能是有效文本的内容进行拆分的需要使我们改变了方法。 SQL 也是动态构建的,带有条件连接等,我们认为最好在代码中而不是在存储过程中完成。这是一个可以有许多参数排列的查询,这就是为什么我们不能使它成为静态 sql。 @Poz:我建议在 SQL 中动态添加足够的占位符,然后将它们指定为参数值。直接包含这些值太冒险了,IMO。 @Poz:我的意思是如果你有两个参数,你创建一个IN (?, ?)IN(:p1, :p2) 或其他的IN 子句,然后以正常方式将这些参数值动态添加到命令中.即使 SQL 是动态的,这并不意味着您必须避免使用参数。还有其他替代方法,例如表值参数 (msdn.microsoft.com/en-us/library/bb510489.aspx),具体取决于您使用的 SQL 服务器版本。【参考方案4】:

我使用了 hack-solution: 接口。 查看内置值类型和字符串类型实现的接口:

struct Int32 : IComparable, IFormattable, IConvertible, IComparable<int>, IEquatable<int>

class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string>

struct Boolean : IComparable, IConvertible, IComparable<bool>, IEquatable<bool>

struct DateTime : IComparable, IFormattable, IConvertible, ISerializable, IComparable<DateTime>, IEquatable<DateTime>

struct UInt64 : IComparable, IFormattable, IConvertible, IComparable<ulong>, IEquatable<ulong>

struct Single : IComparable, IFormattable, IConvertible, IComparable<float>, IEquatable<float>

struct Byte : IComparable, IFormattable, IConvertible, IComparable<byte>, IEquatable<byte>

struct Char : IComparable, IConvertible, IComparable<char>, IEquatable<char>

struct Decimal : IFormattable, IComparable, IConvertible, IComparable<decimal>, IEquatable<decimal>

您可以使用IComparable,IConvertible,IEquatable&lt;T&gt;进行约束。 像这样:

 public static void SetValue<T>(T value) where T : IComparable, IConvertible, IEquatable<T>
    
        //TODO:
    

或者您可以使用类型代码无限制地检查数据时间。

public static void SetValue<T>(T value)
    
        switch (Type.GetTypeCode(typeof(T)))
        
            #region These types are not what u want, comment them to throw ArgumentOutOfRangeException

            case TypeCode.Empty:
                break;
            case TypeCode.Object:
                break;
            case TypeCode.DBNull:

                #endregion

                break;
            case TypeCode.Boolean:
                break;
            case TypeCode.Char:
                break;
            case TypeCode.SByte:
                break;
            case TypeCode.Byte:
                break;
            case TypeCode.Int16:
                break;
            case TypeCode.UInt16:
                break;
            case TypeCode.Int32:
                break;
            case TypeCode.UInt32:
                break;
            case TypeCode.Int64:
                break;
            case TypeCode.UInt64:
                break;
            case TypeCode.Single:
                break;
            case TypeCode.Double:
                break;
            case TypeCode.Decimal:
                break;
            case TypeCode.DateTime:
                break;
            case TypeCode.String:
                break;
            default:
                throw new ArgumentOutOfRangeException();
        
    

请记住,不要使用对象类型,而是使用泛型类型作为参数类型。否则,当值为 null 时,您可能会在代码行 Type.GetTypeCode(value.GetType()) 处获得 NULL EXCEPTION。

【讨论】:

【参考方案5】:

在使用类时可以使用静态构造函数来检查类型参数。

class Gen<T> 
    static Gen() 
        if (!typeof(T).IsValueType && typeof(T) != typeof(String))
        
            throw new ArgumentException("T must be a value type or System.String.");
        
    

【讨论】:

这在编译时对您没有帮助,而这才是真正应该使用泛型的地方。在构造函数中抛出异常也是非常粗鲁的,尤其是在静态构造函数中——消费者很可能会在运行时得到一个无用的“TypeInitializerException”并且不知道为什么。 @DanField 如果你眯着眼睛,这可以在编译时帮助你,因为任何引用Gen&lt;T&gt; 的静态构造函数的封闭类型都可能会创建一个警告,它总是会引发异常。只需要 Roslyn 支持的静态分析器,它是一个完整的解决方案恕我直言。可以通过将逻辑移动到静态只读字段来避免静态构造函数问题,这将避免 TypeInitializerException。总的来说,我认为这是对原语的约束进行编码的相当巧妙的方法。 当我写这篇评论时,我认为魔法并不存在 - 但如果它现在存在,也许一个关于如何打开和使用它的新答案将是有帮助的。

以上是关于包含值类型和字符串的 C# 通用约束的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript:相当于 C# 的用于扩展类的通用类型约束?

TypeScript - 通用约束可以提供“允许的”类型吗?

带有字符串参数的构造函数的通用约束?

多映射通用集合类(C#实现)--支持一键多值存储

C#泛型 类型约束

C#数据类型