具有两种可能类型的参数共享公共方法的通用方法

4

我写了一个通用方法,它将与 StringStringBuilder 参数一起使用。它返回参数中第二个单词的位置(单词可以用空格和换行符分隔)。对于使用参数的 []Length(),我没能想到比下面这段丑陋的代码更好的方式。是否有更优雅的方法?

int PositionOfTheSecondWord<T>(T text) // T can be String or StringBuilder
{
    int pos = 0;
    int state = 0;
    char c;

    // Get length of the text
    // UGLY!
    int length = text is StringBuilder ? (text as StringBuilder).Length : (text as String).Length;

    while (pos <= length - 1)
    {
        // Get the next character
        // UGLY!
        c = text is StringBuilder ? (text as StringBuilder)[pos] : (text as String)[pos];

        if (c == ' ' || c == '\n') // space
        {
            if (state == 1)
                state = 2; // 2 means the space between the first and the second word has begun
        }
        else // a letter
            if (state == 0)
                state = 1; // 1 means the first word has begun
            if (state == 2)
                return pos;

        pos++;

    }

    return -1;

}

附注:我不能只为字符串参数编写一个函数并从StringBuilder.ToString()中调用它,因为我的StringBuilder可能非常大。


3
我认为更明智的做法是只创建一个重载,似乎不需要使用泛型。 - Michael Christensen
这只是一个简单任务的示例方法。如果我需要实现一个更复杂的任务,涉及到String和StringBuilder,比如说几个屏幕长的代码,你建议我复制粘贴两次吗?那样甚至更丑陋。 - Inego
1
@Inego:看看我的基于多态的答案吧,它避免了代码重复。 - O. R. Mapper
4个回答

4

我认为最好的方法是使用重载方法。例如:

int PostionOfTheSecondWord(string text)
{
    // Code optimized for strings.
}

int PostionOfTheSecondWord(StringBuilder text)
{
    // Code optimized for StringBuilder.
}

这将使你的代码更易读、更易维护,性能也会更好。
希望这能帮助你的学习。

谢谢。当然,我从一开始就考虑过这个问题,但那不是很丑吗?有两个重复的方法,只有参数类型不同?泛型不是旨在使事情更简单吗? - Inego
好的,这是一种始终有效的解决方案。如果您尝试PositionOfTheSecondWord(100),则您的建议将失败-它将编译良好,但在运行时会引发空引用异常。对于既适用于字符串又适用于StringBuilder且没有其他约束的T类型,没有任何约束可以起作用。 - Casperah
2
只有在您不必担心对象的实际类型时(例如,它们始终实现相同的接口或始终派生自相同的基类),泛型才有意义。在您的情况下,由于您处理的是完全不相关的类String和StringBuilder,因此最好在不同的方法中处理它们。 - Alexandre Vinçon
看起来C#似乎不能为我提供比重载方法更好的东西。多么令人悲哀啊。 - Inego

1

根据您的方法长度,这可能是多态的情况:

正如其他答案中所指出的那样,stringStringBuilder彼此之间没有关联。因此,您唯一的机会是为这两种类型创建相关的包装器,以便在它们上面使用相同的方法。

您可以定义一个带有该方法的包装器基类,如下所示:

public abstract class ScannableStringBase
{
    public abstract int Length { get; }

    public abstract char this[int index] { get; }

    public int PositionOfTheSecondWord()
    {
        int pos = 0;
        int state = 0;
        char c;

        int length = this.Length;

        while (pos <= length - 1)
        {
            c = this[pos];

            if (c == ' ' || c == '\n') // space
            {
                if (state == 1)
                    state = 2; // 2 means the space between the first and the second word has begun
            }
            else // a letter
                if (state == 0)
                    state = 1; // 1 means the first word has begun
                if (state == 2)
                    return pos;

            pos++;
        }

        return -1;

    }
}

从那个类派生出处理所需值类型的子类:

public class ScannableString : ScannableStringBase
{
    public ScannableString(string value)
    {
        this.stringValue = value;
    }

    private readonly string stringValue;

    public override int Length {
        get {
            return stringValue.Length;
        }
    }

    public override char this[int index] {
        get {
            return stringValue[index];
        }
    }
}

public class ScannableStringBuilder : ScannableStringBase
{
    public ScannableString(stringBuilder value)
    {
        this.stringBuilder = value;
    }

    private readonly string stringBuilder;

    public override int Length {
        get {
            return stringBuilder.Length;
        }
    }

    public override char this[int index] {
        get {
            return stringBuilder[index];
        }
    }
}

总结一下,您将获得以下优点:

  • 没有代码重复,因为PositionOfTheSecondWord()只在基类中定义了一次。
  • 类型安全,因为您的PositionOfTheSecondWord()方法只能在stringStringBuilder上调用。
  • 可扩展性,因为如果您发现需要支持第三种类型,您可以从ScannableStringBase派生出另一个类。

可能的一个缺点是您必须在某个地方事先区分要分析的类型,以便决定实例化ScannableString还是ScannableStringBuilder


非常感谢您详细的回答!这确实是一个解决方案。不过遗憾的是,C#没有提供更简单的方法来完成这个任务。 - Inego
@Inego:确实,有时我也希望有隐式接口和相应的泛型约束。 - O. R. Mapper

0
int GetPos(string text)
    {
        int length = text.Length;
        for (int i = 0; i < length; i++)
        {
            if (GetChar(text, i) == ' ')
            {
                return i;
            }
        }

        return -1;
    }

    int GetPos(StringBuilder sb)
    {
        int length = sb.Length;
        for (int i = 0; i < length; i++)
        {
            if (GetChar(sb, i) == ' ')
            {
                return i;
            }
        }

        return -1;
    }

    char GetChar<T>(T text, int pos)
    {
        if (text.GetType() == typeof(StringBuilder))
        {
            return (text as StringBuilder)[pos];
        }
        else if (text.GetType() == typeof(String))
        {
            return (text as String)[pos];
        }
        else
        {
            throw new ArgumentException("Wrong parameter, T must be string or StringBuilder");
        }
    }

谢谢,但我认为这并不比我的丑陋代码更优雅 :) 我看到了重载和运行时类型检查的使用。 - Inego

0
如果你想使用一个通用方法,你可以使用这个最简单的代码,达到相同的效果。
// T can be String or StringBuilder:
static int PositionOfTheSecondWordNew<T>(T text) 
{
    int pos = -1;
    string[] word;

    // If T is string or StringBuilder this line is not necessary:
    if ((text is StringBuilder) || (text is string)) 
    {
        word = text.ToString().Split(new char[]{' ', '\n'});
        pos = word[0].Length;
    }
    return pos;
}

很抱歉,您可能没有注意到我代码下面的“P.S.”,它说我无法负担将大型StringBuilder复制到字符串中。 - Inego

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接