13

Is it possible to specify that a enum property can only have a range of values?

enum Type
{
    None,
    One,
    Two,
    Three
}

class Object
{
    [AllowedTypes(Type.One,Type.Three)]
    Type objType { get; set; }
}

Something like this? Maybe some validator in enterprise library that I don't know of ?!

Ty

Hélder Gonçalves
  • 3,822
  • 13
  • 38
  • 63
  • 5
    This defeats the whole purpose of an enum. It is a collection of allowable values. – Cody Gray - on strike May 15 '14 at 13:04
  • 2
    An enum is already supposed to be range of allowed values. – Rik May 15 '14 at 13:05
  • 10
    ... and you'd never want to restrict that to a subset for a particular property, guys? – Rawling May 15 '14 at 13:06
  • 2
    No, I would create a second (or third) enum. @rawling – Cody Gray - on strike May 15 '14 at 13:08
  • 5
    @CodyGray but that makes the values instances of different types, which can be very inconvenient. – phoog May 15 '14 at 13:10
  • 1
    But if they can't be used interchangeably, they *are* different types. It's like the difference between `int` and `long`. – Cody Gray - on strike May 15 '14 at 13:11
  • 1
    @CodyGray consider this: `enum VehicleType { Car, Truck, Motorcycle, Bus }` and `class ParkingSpace { public VehicleType Content { get; set; } }` where the Content property only allows Car or Motorcycle. I don't think that in this case a new enum `VehiclesAllowedInThisParkingSpace` is appropriate. – phoog May 15 '14 at 13:17
  • @CodyGray consider the function `GetVehicleWeightRangeByType(VehicleType vehicleType)` and that you might want to pass the return value of `parkingSpace.Content`. If the type is `VehiclesAllowed...` then you have to cast. You also probably want to ensure that the two enums are synchronized so identically-named fields have the same numeric value. Oh, the pain! – phoog May 15 '14 at 13:20
  • @phoog In this case, VehicleType would be set to any of the values and GetVehicleWeightRangeByType would validate if the value is allowed, imo. – Viezevingertjes May 15 '14 at 13:23
  • @MrMichael The function has no business validating the value passed to it based on the allowed values in a parking space; it doesn't even know whether the value is coming from an instance of ParkingSpace. It could be coming from an instance of `CommercialVehicleRoadInspectionReport` instead. – phoog May 15 '14 at 13:30
  • 1
    @Cody `But if they can't be used interchangeably, they are different types`: So if I'm validating that an `int` is even, I should be defining and using an `EvenInt` type instead? If I'm validating that a `string` is not empty, I should create a `NonEmptyString ` class? – Rawling May 15 '14 at 13:31
  • 1
    We can discuss the "use" of this for hours i think, but the real answer is; this is not possible and for a reason. The only way is shown below and it's runtime validation only. It looks like you need some kind of inheritance here, but that is not possible with enums. – Viezevingertjes May 15 '14 at 13:38
  • Right, so far several of the commenters and answerers have been confusing run-time validation with compile-time validation. I was talking about the latter, and I understood the question to be doing the same. Yes, @Rawling, if you wanted compile-time validation for parameters of those types, that is precisely what you would have to do. – Cody Gray - on strike May 15 '14 at 14:05
  • Of course, the joy of enums is that you can't *really* check them until run-time anyway. – Rawling May 15 '14 at 14:12

4 Answers4

8

You could do the validation in setter logic.

EDIT: some example:

class Object
{
    private Type _value;

    public Type objType{ 

        get{ return _value; }
        set{
            if(value != Type.One && value != Type.Three)
                throw new ArgumentOutOfRangeException();
            else
                _value = value;
        }
    }
}
st4hoo
  • 2,196
  • 17
  • 25
1

Same as st4hoo's solution, only with reflection. You can make it as crazy as you would like.

using System;
using System.Linq;

namespace ConceptApplication
{
    static class Program
    {
        static void Main(string[] args)
        {
            var foobar = new Foobar
            {
                SomeProperty = Numbers.Five
            };
        }
    }

    public class Foobar
    {
        private static Numbers _someProperty;

        [NumberRestriction(Allowed = new[] {Numbers.One, Numbers.Two})]
        public Numbers SomeProperty
        {
            get { return _someProperty; }
            set
            {
                RestrictionValidator.Validate(this, "SomeProperty", value);

                _someProperty = value;
            }
        }
    }

    public class NumberRestriction : Attribute
    {
        public Numbers[] Allowed { get; set; }
    }

    public static class RestrictionValidator
    {
        public static void Validate<T>(T sender, string propertyName, Numbers number)
        {
            var attrs = sender.GetType().GetProperty(propertyName).GetCustomAttributes(typeof(NumberRestriction), true);

            if (attrs.OfType<NumberRestriction>().Any(attr => !(attr).Allowed.Contains(number)))
                throw new ArgumentOutOfRangeException();
        }
    }

    public enum Numbers
    {
        One,
        Two,
        Three,
        Four,
        Five
    }
}
Viezevingertjes
  • 1,507
  • 1
  • 13
  • 25
1

Another way to approach this is through front-end validation / filtering. I encountered a scenario where, on the db level, I needed to allow the db to save a field to any of the enum values, but I didn't want the end user to see invalid options. So, I created an attribute for the enum values specifying what kind of user could see those enum values.

public class FieldAttribute : Attribute
{
    public int FilterProperty { get; set; }
}

Then I decorated the enum values with that attribute.

public enum myEnum{

    ZeroValue,

    [Field(FilterProperty = 0)]
    FirstValue,

    [Field(FilterProperty = 1)]
    SecondValue,

    [Field(FilterProperty = 0)]
    ThirdValue,

    [Field(FilterProperty = 1)]
    FourthValue,
}

then i created an extension method that provided a dictionary of only the values that type of user could see.

        public static IDictionary<int, string> PrepareAcceptableValues(int filterVal) {
        var values = Enum.GetValues(typeof(myEnum));

        var retval = new List<myEnum>();
        foreach (var value in values) {

            try { //if enum value has an attribute type...
                var assignedFilterProp = value.GetType().GetMember(value.ToString()).First().GetCustomAttribute<FieldAttribute>().FilterProperty;

                if (assignedFilterProp.Equals(filterVal)) //if enum value has the correct filter property
                    retval.Add((myEnum)value); //add it in
            }
            catch (Exception e) {
                retval.Add((myEnum)value); //if enum value has no attribute, add it in
            }
        }

        return retval.ToDictionary(i => (int)i, i => i.toString());

Then, when the data came in on the front end, as long as whatever they pick is part of the base enum, the DB can save it.

Not really answering your question directly, but an indirect route that might help you get what you're after.

0

You could do a workaround like this:

static MyEnumType[] allowedEnumTypes = {MyEnumType.One, MyEnumType.Two};

MyEnumType _myEnumObject = allowedEnumTypes.First();
MyEnumType MyEnumObject
{
    get
    {
        return _myEnumObject;
    }
    set
    {
        if(!allowedEnumTypes.Any (et => et == value))
        {
            throw new Exception("Enum value not allowed.");
        }
        _myEnumObject = value;
    }
}
Tarec
  • 3,268
  • 4
  • 30
  • 47