1

Is there a way to do something like "string.Compare()", but for generic types. I want to check the range of some property values.
Here is what I am doing as a work around, but it is pretty ugly:

public class SomeClass<T>
{

    public T MinValue { get; set; }
    public T MaxValue { get; set; }

    private T _value;
    public T Value
    {
        get { return _value; }
        set
        {
            _value = value;

            // Restrict range to Min/Max
            if (Comparer<T>.Default.Compare(MaxValue, value) < 0)
                _value = MaxValue;
            if (Comparer<T>.Default.Compare(MinValue, value) > 0)
                _value = MinValue;
        }
    }
}
stackMeUp
  • 522
  • 4
  • 16
  • 4
    Nope, that looks good to me. This is what the BCL does (see the "references" list on the left here: https://source.dot.net/#System.Private.CoreLib/src/System/Collections/Generic/Comparer.CoreCLR.cs,78f450d4aef50299,references) – canton7 Mar 06 '20 at 11:05
  • 4
    What you have is fine, but an enhancement could be to add an optional constructor parameter of type `IComparer` in case the caller wants to specify a custom comparison. (If none is specified, you'd fall back to using `Comparer.Default` like you already do.) – Matthew Watson Mar 06 '20 at 11:09
  • 1
    You can introduce `private IComparer _comparer = Comparer.Default;` at the top of class, and make `SomeClass` generic – Pavel Anikhouski Mar 06 '20 at 11:14
  • 2
    The class must actually be already declared as `public class SomeClass` otherwise the code in the OP wouldn't compile. I assume the OP just forgot it. – Matthew Watson Mar 06 '20 at 11:16
  • You could also add your min, max, (or other validation values) as data annotations on the properties themselves, and then via reflection access properties and then ask for these validation annotations. As long as you are applying the same data annotations on all classes naturally. Then any class with these annotation will be validated. Don't even need to overload the comparer, but you could do that too. Check out the link: https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/models-data/validation-with-the-data-annotation-validators-cs – Morten Bork Mar 06 '20 at 11:55
  • @Morten Bork, if you are referring to creating my own attribute for Min/Max, note that it is not possible with generic types – stackMeUp Mar 06 '20 at 12:15
  • 1
    @stackMeUp if you use reflection, process through each property, asking if it has a data annotation of a min-max type it is. https://stackoverflow.com/questions/7027613/how-to-retrieve-data-annotations-from-code-programmatically – Morten Bork Mar 06 '20 at 12:16
  • @stackMeUp just edited Of course, the guy is using a "player name" on an object, doesn't matter, just add a min max, or whatever instead. If someone doesn't beat me to it, I will add a more apt example later today – Morten Bork Mar 06 '20 at 12:17
  • @Morten Bork, not too sure how I can do that, but will have a go at it, thanks :-) – stackMeUp Mar 06 '20 at 12:20
  • @stackMeUp Add an answer for you to look at :) – Morten Bork Mar 06 '20 at 13:44

1 Answers1

0

This code demonstates what I was talking about in my comment. Of course you will have to modify it to fit with your precise paradigm, of using it in a comparer, but this should be clear enough...

public class Program
    {
        public static void Main(string[] args)
        {
            System.Console.WriteLine("Hello World!");

            TestObject testObject = new TestObject(15);

            TestObject testObject2 = new TestObject(9);

            TestObject testObject3 = new TestObject(31);

            System.Console.ReadLine();
        }
    }

public class TestObject
    {
        [ValidateIntMin(Min = 10)]
        [ValidateIntMax(30)]
        public int SomeInt { get; set; }

        public TestObject(int value)
        {
            SomeInt = value;

            if (!Validator.Validate(this))
            {
                System.Console.WriteLine("Invalid Value assigned: " + value);
            }
            else
            {
                System.Console.WriteLine("" + SomeInt + " was a valid value");
            }

        }
    }

public class ValidateIntMax : Attribute
    {
        public int Max { get; set; }

        public ValidateIntMax(int MaxValue)
        {
            Max = MaxValue;
        }
    }

public class ValidateIntMin: Attribute
    {
        public int Min { get; set; }
    }

public static class Validator
    {
        public static bool Validate<T>(T input) {
            var attrType = typeof(T);
            var properties = attrType.GetProperties();
            bool isValid = true;
            foreach (PropertyInfo propertyInfo in properties)
            {
                var customerMaxValueInt = propertyInfo.GetCustomAttributes(typeof(ValidateIntMax), false).FirstOrDefault();
                var customerMinValueInt = propertyInfo.GetCustomAttributes(typeof(ValidateIntMin), false).FirstOrDefault();

                if (customerMaxValueInt != null)
                {
                    if (propertyInfo.PropertyType == typeof(int))
                    {
                        var currentPropertyInfoBeingTested = (int)propertyInfo.GetValue(input);
                        var currentMaxValueToVerifyAgainst = ((ValidateIntMax)customerMaxValueInt).Max;

                        if (currentPropertyInfoBeingTested > currentMaxValueToVerifyAgainst)
                        {
                            isValid = false;
                        }
                    }
                }

                if (customerMinValueInt != null)
                {
                    if (propertyInfo.PropertyType == typeof(int))
                    {
                        var currentPropertyInfoBeingTested = (int)propertyInfo.GetValue(input);
                        var currentMaxValueToVerifyAgainst = ((ValidateIntMin)customerMinValueInt).Min;

                        if (currentPropertyInfoBeingTested < currentMaxValueToVerifyAgainst)
                        {
                            isValid = false;
                        }
                    }
                }

            }
            return isValid;
        }
    }

Should give the output:

Hello World!

15 was a valid value

Invalid Value assigned: 9

Invalid Value assigned: 31

Of course you can add validation for different types, etc. This is just to show a totally custom way of setting up your attributes.

I recommend you read up on the ValidationAttribute however, to see if you can't use the implemented functionality.

But this is just a PoC piece.

Morten Bork
  • 1,413
  • 11
  • 23
  • Thanks Morten Bork, nice example. Ideally I would want "[ValidateIntMax(T MaxValue)]" though as Min/Max values are defined at runtime in my case but I don't think it is possible using generic types. – stackMeUp Mar 06 '20 at 13:49