Search code examples
c#performanceicomparer

Sort a list in a specific order comparer


I have a list of strings and I need to write an IComparer implementation to sort them in a specific order.

My current implementation:

public enum TimeBucket
{
    [Description("0D")]
    ZeroDay,
    [Description("1D")]
    OneDay,
    [Description("1W")]
    OneWeek,
    [Description("2W")]
    TwoWeek,
    [Description("0M")]
    ZeroMonth,
    [Description("1M")]
    OneMonth
}

public class TimeBucketComparer : IComparer
{
    public static TimeBucketComparer Instance { get; } = new TimeBucketComparer();

    private TimeBucketComparer()
    {

    }

    public int Compare(object x, object y)
    {
        TimeBucket xvar = GetValue(string.Join("", x.ToString().Split(' ')));
        TimeBucket yvar = GetValue(string.Join("", y.ToString().Split(' ')));

        if (EqualityComparer<TimeBucket>.Default.Equals(xvar, default(TimeBucket)) &&
            EqualityComparer<TimeBucket>.Default.Equals(yvar, default(TimeBucket)))
            return String.CompareOrdinal(xvar.ToString(), yvar.ToString());

        if (EqualityComparer<TimeBucket>.Default.Equals(xvar, default(TimeBucket))) return -1;
        if (EqualityComparer<TimeBucket>.Default.Equals(yvar, default(TimeBucket))) return 1;

        return xvar.CompareTo(yvar);
    }

    public TimeBucket GetValue(string description) => EnumExtensions.GetValueFromDescription<TimeBucket>(description);


}
public static class EnumExtensions
{
    public static string GetDescription(this Enum value)
    {
        return ((DescriptionAttribute)Attribute.GetCustomAttribute(value.GetType().GetFields(BindingFlags.Public | BindingFlags.Static).Single(x => x.GetValue(null).Equals(value)),typeof(DescriptionAttribute)))?.Description ?? value.ToString();
    }

    public static T GetValueFromDescription<T>(string description)
    {
        var type = typeof(T);
        if (!type.IsEnum) throw new InvalidOperationException();
        foreach (var field in type.GetFields())
        {
            var attribute = Attribute.GetCustomAttribute(field,
                typeof(DescriptionAttribute)) as DescriptionAttribute;
            if (attribute != null)
            {
                if (attribute.Description == description)
                    return (T)field.GetValue(null);
            }
            else
            {
                if (field.Name == description)
                    return (T)field.GetValue(null);
            }
        }
        throw new ArgumentException("Not found.", "description");
        // or return default(T);
    }
}

My current implementation takes a lot of time because of the reflection involved. I used this way because of the need for a generic implementation. Input has at least 40000 records of Timebuckets which need to be sorted. The sort algorithm is linq orderby which is generic and can't be modified. Hence, need to use a comparer.

I need to sort strings. Is there a better approach which doesn't use reflection?

Edit: In case it's not clear, my input is {"1M", "1D", "1W", "0D"} and the output i require is {"0D", "1D", "1W", "1M"}


Solution

  • I didn't get your sorting logic but anyway - if you have perfomance problems because of reflection - just do reflection once and cache the results. For example:

    public class TimeBucketComparer : IComparer, IComparer<string> {
        public static TimeBucketComparer Instance { get; } = new TimeBucketComparer();
    
        private static readonly Lazy<Dictionary<string, TimeBucket>> _values = new Lazy<Dictionary<string, TimeBucket>>(() => {
            // get all fields and store in dictionary, keyed by description attribute
            return typeof(TimeBucket)
                .GetFields(BindingFlags.Public | BindingFlags.Static)
                .ToDictionary(c => 
                    c.GetCustomAttribute<DescriptionAttribute>()?.Description ?? c.Name, 
                    c => (TimeBucket) c.GetValue(null));
        });
    
        private TimeBucketComparer() {
    
        }
    
        public int Compare(object x, object y) {
            string xvar = string.Join("", x.ToString().Split(' '));
            string yvar = string.Join("", y.ToString().Split(' '));
            return Compare(xvar, yvar);   
        }
    
        public int Compare(string x, string y) {
            if (!_values.Value.ContainsKey(x))
            {
                // do something, invalid value
                throw new ArgumentException(nameof(x));
            }
            if (!_values.Value.ContainsKey(y))
            {
                // do something, invalid value
                throw new ArgumentException(nameof(y));
            }
            return _values.Value[x].CompareTo(_values.Value[y]);
        }
    }