Jonathan Wood
Jonathan Wood

Reputation: 67223

Write generic function for either string or my custom type

I have the following two extension methods. (I have a lot more, but I'll use these for this discussion.)

public static MyExtensions
{
    public static int IndexOf(this string s, Func<char, bool> predicate, int startIndex)
    {
        for (int i = startIndex; i < s.Length; i++)
        {
            if (predicate(s[i]))
                return i;
        }
        return -1;
    }

    public static int IndexOf(this StringEditor s, Func<char, bool> predicate, int startIndex)
    {
        for (int i = startIndex; i < s.Length; i++)
        {
            if (predicate(s[i]))
                return i;
        }
        return -1;
    }
}

They both do the same thing. One works with type string. And the other works with type StringEditor, which is a class I created.

My question is if anyone can think of a way to implement both in a single method using generics.

I cannot modify string, but I can make any changes needed to StringEditor.

I cannot derive StringEditor from string because string is sealed.

I couldn't find any interface implemented by string for accessing individual characters that I could implement in StringEditor. IEnumerable<T> is available but does not support accessing individual characters directly like an array.

And it isn't valid to make a type argument for both string and StringEditor.

public static int IndexOf<T>(this T s, Func<char, bool> predicate, int startIndex) where T : string, StringEditor

I don't think this can be done. But maybe someone is more clever than me?

Update

Where I described that I needed to directly access individual characters, I meant using the s[i] syntax. In addition to generally being less performant, IEnumerable<T> does not support this.

While IEnumerable<T> could be used to perform the results of my two methods here, it does not allow direct character access.

Upvotes: 0

Views: 93

Answers (2)

PMF
PMF

Reputation: 17248

You could use ReadOnlySpan<char> as argument type.

public static int IndexOf(this ReadOnlySpan<char> s, Func<char, bool> predicate, int startIndex)

ReadOnlySpan<T> supports indexing. Since there's an implicit conversion from string to ReadOnlySpan<char> you don't even need to modify the call sites. And there's no measurable performance penalty with this conversion, since creating a Span is basically free, both in time as well as in memory usage.

Upvotes: 1

John Wu
John Wu

Reputation: 52250

Generics not needed. Use IEnumerable<char>, which is implemented by String and could be implemented by your StringEditor.

public static int IndexOf(this IEnumerable<char> s, Func<char, bool> predicate, int startIndex) 
{
    int i = startIndex;
    foreach (char c in s.Skip(startIndex))
    {
        if (predicate(c))
            return i;
        i++;
    }
    return -1;
}

Or

public static int IndexOf(this IEnumerable<char> source, Func<char, bool> predicate, int startIndex) 
{
    var s = source.ToArray();
    for (int i = startIndex; i < s.Length; i++)
    {
        if (predicate(s[i]))
            return i;
    }
    return -1;
}

Upvotes: 1

Related Questions