C# 是否可以将泛型类型参数约束为字符串或数组

C# 是否可以将泛型类型参数约束为字符串或数组,c#,string,generics,C#,String,Generics,简而言之:是否可以定义一个通用方法,其中类型参数(T)被约束为string或int[]?在pseudo-C#中,我想做的是: public static int MyMethod<T> ( T arg1, T arg2 ) where T : (has an indexer that returns an int) { // stuff with arg1[i], arg2[j], etc... } 根据线程,我意识到我不能在where t:x..constrai

简而言之:是否可以定义一个通用方法,其中类型参数(
T
)被约束为
string
int[]
?在pseudo-C#中,我想做的是:

public static int MyMethod<T> ( T arg1, T arg2 ) 
    where T : (has an indexer that returns an int) {
    // stuff with arg1[i], arg2[j], etc...
}
根据线程,我意识到我不能在
where t:x..
constraint子句中列出未关联的类型。
int[]
string
都符合
T:IEnumerable
,但
IEnumerable
不要求实现者拥有索引器,这正是我在这两种类型中使用的共同特性

这背后的目的是,我正在构建一些高度优化的字符串解析和分析函数,例如Damerau–Levenshtein距离算法的快速实现。我发现,首先将字符串转换为int数组有时可以在重复的逐字符处理中产生更快的执行速度(如D-L算法)。这主要是因为比较
int
值比比较
char
值快得多

关键词是“有时”。有时直接对字符串进行操作会更快,并且避免了首先转换和复制到
int
数组的成本。所以我现在有了除了声明之外完全相同的方法

当然,我可以使用
dynamic
,但是运行时检查的性能损失完全破坏了在方法构造过程中获得的任何收益。(我做了测试)

不,C#不允许您创建这样的“复合”约束

你表达的意思是一样的,但你不能利用这一事实为自己谋利。C语言的泛型与C++模板相似,但它们的工作方式完全不同,因此索引器做同样的事情最终是不相关的。
当然,您可以使用所需的特定类型创建两个重载,但这确实意味着一些恼人的复制和粘贴。任何为了避免重复而对其进行抽象的尝试都会严重影响性能。

您可以对方法进行签名

public static int MyMethod<T> ( T[] arg1, T[] arg2 )
公共静态int-MyMethod(T[]arg1,T[]arg2)

并在传递字符串参数之前使用String.tocharray()(或者在重载中,您会想到…)

您不能有一个说“类型必须有一个索引器”的约束

但是,您可以有一个约束,即“类型必须实现具有索引器的接口”。例如,这样的接口可能是
IList

不幸的是,
string
没有实现
IList
,因此您必须为它编写一个小的包装类:

sealed class StringWrapper : IList<char>
{
    public string String { get; private set; }
    public StringWrapper(string str) { String = str; }
    public static implicit operator StringWrapper(string str)
    {
        return new StringWrapper(str);
    }

    public char this[int index]
    {
        get { return String[index]; }
        set { throw new NotSupportedException(); }
    }

    // Etc.... need to implement all the IList<char> methods
    // (just throw NotSupportedException except in the ones that are trivial)
}
密封类StringWrapper:IList
{
公共字符串{get;private set;}
公共StringWrapper(string str){string=str;}
公共静态隐式运算符StringWrapper(字符串str)
{
返回新的StringWrapper(str);
}
public char this[int index]
{
获取{返回字符串[索引];}
设置{抛出新的NotSupportedException();}
}
//等等……需要实现所有IList方法
//(只需抛出NotSupportedException,除了那些无关紧要的例外)
}
然后你可以这样声明你的方法:

public static TElement MyMethod<TCollection, TElement>(TCollection arg)
    where TCollection : IList<TElement>
{
    return arg[0];
}

[...]

MyMethod<StringWrapper, char>("abc")     // returns 'a'
MyMethod<int[], int>(new[] { 1, 2, 3 })  // returns 1
公共静态远程通信MyMethod(TCollection arg)
where t集合:IList
{
返回参数[0];
}
[...]
MyMethod(“abc”)//返回“a”
MyMethod(new[]{1,2,3})//返回1

静态地将
T
视为一种类型或另一种类型在什么意义上是明智的?约束的要点是允许您对
T
类型的变量进行操作,并期望具有最低级别的类型兼容性。约束到两个互不兼容的类型是很奇怪的。@kirk woll:正如我详细描述的,它们在我看来并不是互不兼容的。相反,它们是相同的。我已经构建并正在使用100%相同的方法,除了一个版本中的参数声明为
string
,而另一个版本中的参数声明为
int[]
。在这两种情况下,我都将集合的每个成员作为整数进行操作。否。
字符串
和(任意)
数组
对象
上统一,字符串在C#中不是
字符[]
,虽然可以从字符串创建
字符[]
,但这是一个新的数组对象,其内容已复制。@pst:字符串索引器,也别名为Chars属性,(请参阅)提供对字符的直接索引访问,无需复制。它与ToCharray()不同,ToCharray()将内容复制到一个新对象。@jmh#u gr类型在C#中仍然是主格的,而不是结构化的。唯一的问题是OP是“构建一些高度优化的字符串解析和分析函数”-这根本不会很快。唉,这违背了使用字符串索引器的目的,即在不复制内容的情况下进行索引访问。如果我要转换成一个数组,我只需要转换成一个
int
的数组,用扩展方法很容易就可以了。这确实有效。非常巧妙地使用
隐式运算符
语句。我修改了
StringWrapper
示例以实现
IList
,将我的方法约束为
T:IList
,并使用编译成功的
string
int[]
参数调用这些方法。不幸的是,它仍然非常慢,所以我实际上不会使用它。(与单独的显式
int[]
string
重载相比,
dynamic
和提供的
IList
包装版本都慢了2.8倍)。我很惊讶你说它和
dynamic
一样慢,但你似乎已经测试过了,所以我相信你。我希望这意味着dynamic的速度惊人地快,而不是接口方法分派的速度惊人地慢
public static TElement MyMethod<TCollection, TElement>(TCollection arg)
    where TCollection : IList<TElement>
{
    return arg[0];
}

[...]

MyMethod<StringWrapper, char>("abc")     // returns 'a'
MyMethod<int[], int>(new[] { 1, 2, 3 })  // returns 1