如何使用C#泛型代替字符串和byte []

时间:2009-07-31 21:35:51

标签: c# generics

是否可以使用C#generics将这4个例程替换为一个?

int memcmp (string a, string b){...}
int memcmp (string a, byte[] b){...}
int memcmp (byte[]a,  string b){...}
int memcmp (byte[]a,  byte[] b){...}

我尝试了很多变化,但无法准确确定要使用的内容......

例如......

int memcmp<A, B>( A a, B b) 
{
  if ( a.Length < b.Length ) return 1;
  for ( int i = 0 ; i < a.Length ; i++ )
  {
    if ( a[i] != b[i] ) return ( a[i] < b[i] ) ? -1 : 1;
  }
 }

给出以下错误:

  • 'A'不包含'Length'的定义
  • 无法将带[]的索引应用于“A”
  • 类型的表达式

讨论这个的好参考在哪里?

**注意:**我不是在寻找如何比较字符串和字节的解决方案,而是寻求使用“概念验证”问题来理解泛型如何在C#中工作

7 个答案:

答案 0 :(得分:6)

您理想的解决方案将具有以下特征:

int memcmp<T>( T a, T b ) where T : IHasLengthAndIndexer

其中IHasLengthAndIndexer定义为:

public interface IHasLengthAndIndexer
{
    int Length { get; }
    byte this[ int index ] { get; }
}

然后你可以传入任何类型,假设类型实现IHasLengthAndIndexer

但是,您无法更改byte[]string的界面,因此此解决方案不会取代您的四种方法。

但是,您可以创建自己的类,该类具有来自byte[]string的隐式运算符,并创建一个比较该类实例的方法,但这不是使用泛型。

答案 1 :(得分:5)

除非您具有明确要求作为类型参数传递的任何类型具有此类成员的类型约束,否则无法对泛型类型参数的值调用成员。这是通过将它们限制在特定界面来完成的(你不能只说“我想要任何A,只要它有Length。”

在你的情况下,没有好方法可以做到这一点,因为string和数组之间没有提供Length(或Count或类似内容)的通用接口。数组实现IList<T>,但遗憾的是string没有。两者都实现IEnumerable<T>,但这没有任何方法可以快速检索计数(您可以使用Enumerable.Count,但它实际上将迭代整个字符串以计算字符数。)

答案 2 :(得分:2)

我当然会将其归类为奇怪的代码,但通过查看您使用的类型,您可以找到一些共性:

public static IEnumerable<int> Do<T1,T2>(IEnumerable<T1> one, IEnumerable<T2> two) 
    where T1 : IComparable where T2 : IComparable 
{
    var arr1 = one.ToArray();
    var arr2 = two.ToArray();
    for (int i = 0; i < arr1.Length; i++)
        yield return arr1[i].CompareTo(arr2[i]);

}

char和byte是可比较的。此方法现在返回一个迭代器,该迭代器将给出char到char或char的每个比较结果到同一索引上的字节。这种方法肯定适用于其他类型,前提是它们可以枚举,并且可以比较它们的元素。

答案 3 :(得分:1)

这是“泛型不是模板”的另一个实例 - 泛型依赖于由类型约束(显式或隐式)定义的多态,而C ++模板更像是复杂的宏,它可以让你通过鸭子打字或“静态”多态性”。

建议的int memcmp<A, B>( A a, B b)是可以在C ++中使用的东西,只要类型暴露operator[](int)并且具有Length属性(嗯,实际上要具有属性,它必须是C ++ / CLI ...),因为替换是在编译时完成的。

相反,在C#中,我们只知道参数是对象,不一定是同一类型(没有类型约束) - 所以我们只能使用object的方法。

您可以为byte[]string撰写专门的转化内容,例如Tinster的IHasLengthAndIndexer并拥有int memcmp(IHasLengthAndIndexer a IHasLengthAndIndexer b),但这不需要任何泛型。

从库中,最常用的泛型是类似强类型的集合(重要的是对象共享一个类型,而不是类型的类型);和函数类型(委托,事件处理程序等)。除了将行为抽象到基础库抽象类型之外,我还使用它们,以便消费程序集中的具体类型可以将自己的类型信息注入共享库 - 一个简单的例子就像class A : Factory<A> where A : new()

答案 4 :(得分:1)

没有约束的通用参数被用作类型为System.Object的通用参数,这就是编译器争论使用a.Length的原因。

要获得泛型参数类型的类型特定语法,您需要添加约束,例如:

public void Count<T>(T list) where T : IList

Stringbyte[]只有两个看似常见的接口,IEnumerableIEnumerable<T>,所以它是唯一可用的东西。但是,stringIEnumerable<char>byte[]IEnumerable<byte>,因此我们需要回退到IEnumerable

public int MemCmp<TEn>(TEn obj1, TEn obj2) where TEn : IEnumerable 
{
    IEnumerator enumerator1 = obj1.GetEnumerator();
    IEnumerator enumerator2 = obj2.GetEnumerator();

    bool has1Next;
    bool has2Next;

    do
    {
        has1Next = enumerator1.MoveNext();
        has2Next = enumerator2.MoveNext();

        if(has1Next && has2Next)
        {
            if (enumerator1.Current is IComparable)
            {
                int comparison = ((IComparable) enumerator1.Current).CompareTo(enumerator2.Current);

                if (comparison != 0) return (comparison > 0) ? 1 : -1;
            }
            else if (enumerator2.Current is IComparable)
            {
                int comparison = ((IComparable) enumerator2.Current).CompareTo(enumerator1.Current);

                if (comparison != 0) return (comparison > 0) ? -1 : 1;
            }
            else
            {
                throw new ArgumentException("Not comparable types");
            }
        }
        else 
        {
            if(has1Next)
            {
                return 1;
            }
            else
            {
                return -1;
            }
        }
    } while (true);
}

用法:

MemCmp<IEnumerable>(a, b);

这是实施方法的一种可能的解决方案。

但是,由于这里固有的类型不可接受性以及泛型的静态编译,我们失去了强类型。所以,问题的答案是肯定的,但成本相当高。即使字符和字节在转换为IComparable时也会被装箱。

另请注意您的代码

if ( a[i] != b[i] ) ...

的工作原因是bytechar之间的内置隐式转换,无法用泛型来模仿。

答案 5 :(得分:0)

为什么不直接使用每种类型的.Compare函数? C#中的每个类型都派生自基础对象类。您还可以编写一个函数,该函数接收两个对象并进行比较。

string firstName = "Jason";
string lastName = "Williams";

firstName.CompareTo(lastName);

答案 6 :(得分:0)

我认为仿制药不适合这里。

要修复错误,您需要将A(使用泛型类型约束)约束到具有索引器属性和Length属性的接口。但是,这不是你想要做的。

根据您当前的实现,方法重载有什么问题?