19

Is it possible to make attribute which can limit minimum or maximum value of numbers.

Example:

[MinValue(1), MaxValue(50)]
public int Size { get; set; }

and when i do Size = -3; value of Size must be 1.

I searched in Google and can't find single example about this behavior, maybe because it is not possible to make?

I'm gonna use these attributes in property grid therefore having automatic validation can be handy.

Currently I workaround like this to limit minimum value:

    private int size;

    [DefaultValue(8)]
    public int Size
    {
        get
        {
            return size;
        }
        set
        {
            size = Math.Max(value, 1);
        }
    }

So this acts like MinValue(1)

Jaex
  • 4,204
  • 2
  • 34
  • 56
  • 1
    The PropertyGrid class is not going to help you implement this, it doesn't know anything about your custom attribute. Writing the custom TypeConverter is painful. Enforcing the range in your property setter is the simple approach. – Hans Passant Nov 16 '13 at 14:06

5 Answers5

5

Although it is possible to create a custom attribute, attributes are just metadata for the member they annotate, and cannot change its behavior.

So, you won't get the behavior you want with a plain attribute. You need something to process the attributes in order to enact the desired behavior.

Take a look at TypeConverters for a possibility.

Jordão
  • 55,340
  • 13
  • 112
  • 144
3

Yes, it is possible. Read about custom attributes at MSDN.

And by the way, there is already a solution you can use. It is the RangeAttribute which lets you specify the numeric range constraints for the value of a data field. Read more about it on MSDN.

Ondrej Janacek
  • 12,486
  • 14
  • 59
  • 93
  • 1
    I checked MSDN already and there is nothing there about modifying set value of property with attribute. Also i checked RangeAttribute and it is for asp.net data controls and won't have any effect when i use it with property grid. – Jaex Nov 16 '13 at 13:51
  • Yeah, it seems you don't understand a purpose of attributes. Jordao explains it in his answer. I just posted an answer on a question you are asking "Is it possible to make attribute which can limit minimum or maximum value of numbers.". – Ondrej Janacek Nov 16 '13 at 13:54
  • @Jaex where do you see it's for asp only?? – Noctis Nov 16 '13 at 13:54
  • @OndrejJanacek even though it's a "read on msdn", I upvoted for the new things I learned :) Cheers – Noctis Nov 16 '13 at 13:55
  • @Noctis In here: http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations(v=vs.110).aspx "The System.ComponentModel.DataAnnotations namespace provides attribute classes that are used to define metadata for ASP.NET MVC and ASP.NET data controls." – Jaex Nov 16 '13 at 13:56
  • @Noctis Why should I try to best explanations and examples on msdn when they are so good? It surely is not only a linq answer, but MSDN is like an encyclopedia. – Ondrej Janacek Nov 16 '13 at 13:56
  • @jaex the link you gave is for data annotations, the one he links to is to custom attributes – Noctis Nov 16 '13 at 14:00
  • @OndrejJanacek true ...true ... (I would have probably pasted the relevant bits though :) ) – Noctis Nov 16 '13 at 14:00
  • @Noctis He also linked RangeAttribute MSDN link and it is in DataAnnotations namespace. And when you asked where do you see it's for asp only, i linked it to you. – Jaex Nov 16 '13 at 14:14
  • You must add a reference to `System.ComponentModel.DataAnnotations` – toddmo Apr 24 '16 at 16:14
3

You can elegantly solve this problem by using PostSharp by writing simple aspect, free version will be enough for this purpose:

[Serializable]
class RangeAttribute : LocationInterceptionAspect 
{
    private int min;
    private int max;

    public RangeAttribute(int min, int max)
    {
        this.min = min;
        this.max = max;
    }

    public override void OnSetValue(LocationInterceptionArgs args)
    {
        int value = (int)args.Value;
        if (value < min) value = min;
        if (value > max) value = max;            
        args.SetNewValue(value);
    }
}

and then exacly as you want:

class SomeClass
{
    [Range(1, 50)]
    public int Size { get; set; }
}

with normal usage:

var c = new SomeClass();
c.Size = -3;
Console.Output(c.Size);

will output 1.

Konrad Kokosa
  • 16,563
  • 2
  • 36
  • 58
  • what about not change value automatically, how to notify caller? can i just throw an exception? – Brent81 May 08 '17 at 10:11
2

create an extension

public static class Extensions
{
  public static int FixedValue(this int value, int min, int max)
  {
    if (value >= min && value <= max)
      return value;
    else if (value > max)
      return max;
    else if (value < min)
      return min;
    else return 1;
  }
}

And then:

private int size;
public int Size { get { return size.FixedValue(1, 50); }
                  set { size = value.FixedValue(1, 50); } }
Salvador
  • 153
  • 4
  • Honestly... Considering the 'Complexity' of Addind a Error Message to an Invalid Property Value; I find it awkward that @Salvador answer has just 1 vote. I'm giving a thumbs up. 1. I got to (finally) understand Extensions. 2. He got a similar; yet better way to do this than my own. So yes. Thumbs up. –  Nov 27 '21 at 19:45
1

Yes, it is possible with (already pointed) CustomAttributes, but mind, that you will loose the comfort of the auto-properties - because you need to apply resp. check the attribute restriction somewhere and in this case an appropriate place would be the getter of the property, so the interesting part of the problem is the application of the attributes. You can read how to access custom attributes in this MSDN article.

A possible solution for the MaxValue custom attribute can look like this:

// simple class for the example
public class DataHolder
{
    private int a;

    [MaxValue(10)]
    public int A 
    { 
        get
        {
            var memberInfo = this.GetType().GetMember("A");
            if (memberInfo.Length > 0)
            {
                // name of property could be retrieved via reflection
                var mia = this.GetType().GetMember("A")[0];
                var attributes = System.Attribute.GetCustomAttributes(mia);
                if (attributes.Length > 0)
                {
                    // TODO: iterate over all attributes and check attribute name
                    var maxValueAttribute = (MaxValue)attributes[0];
                    if (a > maxValueAttribute.Max) { a = maxValueAttribute.Max; }
                }
            }
            return a;
        }
        set
        {
            a = value;
        }
    }
}


// max attribute
public class MaxValue : Attribute
{
    public int Max;

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

The sample usage:

var data = new DataHolder();
data.A = 12;
Console.WriteLine(data.A);

creates the output:

10

The code for the MinValue will look the same as for the MaxValue but the if condition will be less than instead of greater than.

keenthinker
  • 7,645
  • 2
  • 35
  • 45
  • Thank you for your effort but unfortunately using `size = Math.Max(value, 1);` in get property will be more elegant solution. – Jaex Nov 16 '13 at 14:20
  • That is true, but this is the catch with the attributes - at some point and somewhere they need to be evaluated and applied, otherwise if they aren't used what good are they for. – keenthinker Nov 16 '13 at 14:28
  • Maybe with propertygrid inherit there is possibility of evaluating attribute when setting value. But figuring out that will be too much work so i will continue to use Math.Max solution. – Jaex Nov 16 '13 at 14:32