简体   繁体   中英

Convert delimited string to array enum using generics in C# 2.0

I need to convert a comma delimited string into an array of enum types within a generic class.

The problem is I need to create an array based on the generic T.

Here is my code:

class Program
{
    static void Main(string[] args)
    {
        var testIt = new TestIt<TestValues[]>();
        TestValues[] converted = testIt.Convert("Pizza,Sub");
    }
}

public class TestIt<T> 
{
    public T Convert(string delimitedValues)
    {
        var valueType = typeof(T);
        var elementType = valueType;
        if (!valueType.IsArray)
        {
            throw new Exception("T is not an array");
        }

        if (valueType.HasElementType)
        {
            elementType = valueType.GetElementType();
        }

        var elements = delimitedValues.Split(',');
        foreach (var elementValue in elements)
        {
            var newElement = Enum.Parse(elementType, elementValue.Trim(), true);
            // not sure what I can do with the element here
        }

    }
}

public enum TestValues
{
    Unknown,
    Pizza,
    Sub,
    Burger
}

Any ideas on how I can do this? I'm stumped!

I've tried creating an object array of the enum type, but can't seem to convert it to T.

Please keep in mind, this is .Net Framwork 2.0 so my toolbox is limited.

Thanks for any ideas anyone can provide.

Try this class:

public class TestIt
{
    public static T[] Convert<T>(string delimitedValues)
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException();
        }

        if (delimitedValues == string.Empty)
        {
            return new T[0];
        }

        string[] parts = delimitedValues.Split(',');
        T[] converted = Array.ConvertAll(parts, x => (T)Enum.Parse(typeof(T), x));
        return converted;
    }
}

By convention you use T as the generic parameter instead of T[]

Use it like:

TestValues[] values = TestIt.Convert<TestValues>("Unknown,Pizza");

You can do it even with one less cast:

public static T[] Convert<T>(string delimitedValues) where T : struct
{
    if (!typeof(T).IsEnum)
    {
        throw new ArgumentException();
    }

    if (delimitedValues == string.Empty)
    {
        return new T[0];
    }

    string[] parts = delimitedValues.Split(',');
    T[] converted = new T[parts.Length];

    for (int i = 0; i < parts.Length; i++)
    {
        if (!Enum.TryParse(parts[i], out converted[i]))
        {
            throw new FormatException(parts[i]);
        }
    }

    return converted;
}

If you want, you can even have a version that will return T?[] (so an array of nullable enums)

public static T?[] ConvertNullable<T>(string delimitedValues) where T : struct
{
    if (!typeof(T).IsEnum)
    {
        throw new ArgumentException();
    }

    if (delimitedValues == string.Empty)
    {
        return new T[0];
    }

    string[] parts = delimitedValues.Split(',');
    T?[] converted = new T?[parts.Length];

    for (int i = 0; i < parts.Length; i++)
    {
        if (parts[i] == string.Empty)
        {
            continue;
        }

        T value;

        if (!Enum.TryParse(parts[i], out value))
        {
            throw new FormatException(parts[i]);
        }

        converted[i] = value;
    }

    return converted;
}

Use it like;

TestValues?[] values = TestIt.ConvertNullable<TestValues>(",Unknown,,Pizza,");

Note that this last version doesn't skip "invalid" values, it still throws on them. Simply it transforms string.Empty elements to null . Sadly there is a problem: if you ConvertNullable<TestValues>(string.Empty) it will return a TestValues[0] , but that string could even be converted to TestValues[1] { null } .

Now, if you really want a pizza with everything:

public static class EnumSplitter
{
    public static T[] Convert<T>(string delimitedValues) where T : struct
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException();
        }

        if (delimitedValues == string.Empty)
        {
            return new T[0];
        }

        string[] parts = delimitedValues.Split(',');
        T[] converted = new T[parts.Length];

        for (int i = 0; i < parts.Length; i++)
        {
            if (!Enum.TryParse(parts[i], out converted[i]))
            {
                throw new FormatException(parts[i]);
            }
        }

        return converted;
    }

    public static TArray ConvertArray<TArray>(string delimitedValues) where TArray : IList
    {
        return MethodCache<TArray>.Convert(delimitedValues);
    }

    public static T?[] ConvertNullable<T>(string delimitedValues) where T : struct
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException();
        }

        if (delimitedValues == string.Empty)
        {
            return new T?[0];
        }

        string[] parts = delimitedValues.Split(',');
        T?[] converted = new T?[parts.Length];

        for (int i = 0; i < parts.Length; i++)
        {
            if (parts[i] == string.Empty)
            {
                continue;
            }

            T value;

            if (!Enum.TryParse(parts[i], out value))
            {
                throw new FormatException(parts[i]);
            }

            converted[i] = value;
        }

        return converted;
    }

    public static TArray ConvertNullableArray<TArray>(string delimitedValues) where TArray : IList
    {
        return MethodCache<TArray>.Convert(delimitedValues);
    }

    private static class MethodCache<TArray> where TArray : IList
    {
        public static readonly Func<string, TArray> Convert;

        static MethodCache()
        {
            if (!typeof(TArray).IsArray)
            {
                throw new ArgumentException("TArray");
            }

            Type element = typeof(TArray).GetElementType();
            Type element2 = Nullable.GetUnderlyingType(element);

            if (element2 == null)
            {
                Convert = (Func<string, TArray>)Delegate.CreateDelegate(typeof(Func<string, TArray>), typeof(EnumSplitter).GetMethod("Convert").MakeGenericMethod(element));
            }
            else
            {
                Convert = (Func<string, TArray>)Delegate.CreateDelegate(typeof(Func<string, TArray>), typeof(EnumSplitter).GetMethod("ConvertNullable").MakeGenericMethod(element2));
            }
        }
    }
}

I use the MethodCache<> class to cache the reflection call to the "right" Convert<> method.

Uses:

TestValues[] arr1 = EnumSplitter.Convert<TestValues>("Unknown,Pizza");
TestValues?[] arr2 = EnumSplitter.ConvertNullable<TestValues>("Unknown,,Pizza,");
TestValues[] arr3 = EnumSplitter.ConvertArray<TestValues[]>("Unknown,Pizza");
TestValues?[] arr4 = EnumSplitter.ConvertNullableArray<TestValues?[]>("Unknown,,Pizza,");

With minimal changes to your code: you can use Array.CreateInstance as you already know the array length ( elements.Length after the Split() ), then call Convert.ChangeType() to be able to convert Array to T :

public T Convert(string delimitedValues)
{
    var valueType = typeof(T);
    var elementType = valueType;
    if (!valueType.IsArray)
    {
        throw new Exception("T is not an array");
    }

    if (valueType.HasElementType)
    {
        elementType = valueType.GetElementType();
    }

    var elements = delimitedValues.Split(',');

    var arrayToReturn = Array.CreateInstance(elementType, elements.Length);

    for (int i = 0; i < elements.Length; i++ )
    {
        var newElement = Enum.Parse(elementType, elements[i].Trim(), true);
        arrayToReturn.SetValue(newElement, i);
    }

    return (T)System.Convert.ChangeType(arrayToReturn, valueType);
}
public static T[] ToEnumsArray<T>(this string stringValuesWithDelimeter) where T : struct
    {
        var result = new List<T>();
        if (!string.IsNullOrWhiteSpace(stringValuesWithDelimeter))
        {
            var arr = stringValuesWithDelimeter.Split(',');
            foreach (var item in arr)
            {
                if (Enum.TryParse(item, true, out T enumResult))
                {
                    result.Add(enumResult);
                }
            }
        }
        return result.ToArray();
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM