Enum Replacement

Posts   
 
    
jtgooding
User
Posts: 126
Joined: 26-Apr-2004
# Posted on: 23-Mar-2006 15:36:44   

So like I LOVE partial classes, I love LLBLGen (most days), but the one thing that absolutely drives me nuts is that you can't have a partial or inherited enum.

I am constantly creating new enums just to tack on an extra field or two that I have to track, so I spent some time and wrote an enum replacement, it's not ideal mainly because you have to wait until runtime to determine that you made a mistake (i.e. manually numbered two entries the same.) but it does provide similar functionality at runtime to what enums provide.

I had originally created this as true generics, but the extra overhead to support various ordinal types just made the code confusing.

I appended the DisplayText on to it afterwards to emulate the Description Attribute we use internally on all our existing enums to provide Display Text for an enum name ('i.e. with spaces etc.)

Looking for thoughts on ways to improve it, one thing I know of that would simplify it is the ability to grab the variable name the class is being assigned to for use in the Name field, but I haven't been able to find how to determine that, and that would also require an attribute to not obfusicate the names.

Here is a sample use of it:

    public sealed partial class MyFakeEnumB : CustomEnumBase
    {

        internal MyFakeEnumB(string name)
            : base(null, name, null)
        {
        }

        internal MyFakeEnumB(string name, string displayText)
            : base(null, name, displayText)
        {
        }
        internal MyFakeEnumB(int? ordinal, string name)
            : base(ordinal, name, null)
        {
        }

        internal MyFakeEnumB(int? ordinal, string name, string displayText)
            : base(ordinal, name, displayText)
        {
        }

        public static MyFakeEnumB Internal = new MyFakeEnumB("I"); // 1
        public static MyFakeEnumB External = new MyFakeEnumB(4,"E"); // 4
        public static MyFakeEnumB Unknown = new MyFakeEnumB(7,"U"); // 7
    }

    public sealed partial class MyFakeEnumB
    {
        public static MyFakeEnumB Override = new MyFakeEnumB("O", "zippy"); // 8
    }

    public class TestEnumsB
    {
        public TestEnumsB()
        {
            // Test Simple print
            Console.WriteLine(string.Format("{0} {1}", MyFakeEnumB.External, MyFakeEnumB.External.ToOrdinal()));
            // Test as a parameter to a call
            TestAsParameter(MyFakeEnumB.Internal);
            // Enumerate
            foreach (CustomEnumEntry Entry in MyFakeEnumB.GetValues())
            {
                Console.WriteLine(string.Format("{0} {1}", Entry, Entry.Ordinal));
            }
            Console.WriteLine(string.Format("{0}", MyFakeEnumB.Parse("zippy", true)));
        }

        private void TestAsParameter(MyFakeEnumB myValue)
        {
            Console.WriteLine(string.Format("{0} {1}",myValue.ToString(), myValue.ToOrdinal()));
        }
    }

And here is the base class:

    public struct CustomEnumEntry
    {
        private int _Ordinal;
        private string _Name;
        private string _DisplayText;

        public int Ordinal
        {
            get
            {
                return _Ordinal;
            }
            set
            {
                _Ordinal = value;
            }
        }

        public string Name
        {
            get
            {
                return _Name;
            }
            set
            {
                _Name = value;
            }
        }

        public string DisplayText
        {
            get
            {
                return _DisplayText;
            }
            set
            {
                _DisplayText = value;
            }
        }

        public CustomEnumEntry(int ordinal, string name, string displayText)
        {
            _Ordinal = ordinal;
            _Name = name;
            _DisplayText = displayText;
        }

        public override string ToString()
        {
            if (DisplayText != null)
                return DisplayText;
            else
                return Name;
        }

    }

    public abstract class CustomEnumBase : IEnumerable<CustomEnumEntry>
    {
        private CustomEnumEntry _EnumEntry;
        private static List<CustomEnumEntry> _IndexedEnum = new List<CustomEnumEntry>();

        protected CustomEnumBase(int? ordinal, string name, string displayText)
        {
            if (!ordinal.HasValue)
                ordinal = FindNextOrdinal();
            _EnumEntry = new CustomEnumEntry();
            // must use local fields to verify no dupes
            Ordinal = ordinal.Value;
            Name = name;
            _EnumEntry.DisplayText = displayText;
            // add it to our static list
            _IndexedEnum.Add(_EnumEntry);
        }

        private int? Ordinal
        {
            get
            {
                return _EnumEntry.Ordinal;
            }
            set
            {
                if (_IndexedEnum.FindIndex(OrdinalExists(value)) == -1)
                {
                    _EnumEntry.Ordinal = value.Value;
                }
                else
                    throw new Exception("Ordinal already exists");
            }
        }

        private string Name
        {
            get
            {
                return _EnumEntry.Name;
            }
            set
            {
                if (_IndexedEnum.FindIndex(NameExists(value)) == -1)
                {
                    _EnumEntry.Name = value;
                }
                else
                    throw new Exception("Name already exists");
            }
        }

        private string DisplayText
        {
            get
            {
                return _EnumEntry.DisplayText;
            }
            set
            {
                if (_IndexedEnum.FindIndex(DisplayTextExists(value)) == -1 || value == null)
                {
                    _EnumEntry.DisplayText = value;
                }
                else
                    throw new Exception("DisplayText already exists");
            }
        }

        public static CustomEnumEntry[] GetValues()
        {
          return _IndexedEnum.ToArray();
        }

        protected Predicate<CustomEnumEntry> OrdinalExists(int? ordinal)
        {
            return delegate(CustomEnumEntry EnumItem) { return (ordinal == EnumItem.Ordinal); };
        }

        protected Predicate<CustomEnumEntry> NameExists(string name)
        {
            return delegate(CustomEnumEntry EnumItem) { return (name == EnumItem.Name); };
        }

        protected Predicate<CustomEnumEntry> DisplayTextExists(string displayText)
        {
            return delegate(CustomEnumEntry EnumItem) { return (displayText == EnumItem.DisplayText); };
        }

        private static int FindNextOrdinal()
        {
            int HighestIndex = int.MinValue;
            foreach (CustomEnumEntry Entry in _IndexedEnum)
            {
                if (Entry.Ordinal > HighestIndex)
                    HighestIndex = Entry.Ordinal;
            }
            if (HighestIndex == int.MinValue)
                HighestIndex = 0;
            HighestIndex++;
            return HighestIndex;
        }

        public override string ToString()
        {
            if (_EnumEntry.DisplayText != null)
                return _EnumEntry.DisplayText;
            else
                return _EnumEntry.Name;
        }

        public int ToOrdinal()
        {
            return _EnumEntry.Ordinal;
        }

        public static int Parse(string enumName)
        {
            return Parse(enumName, false);
        }
        public static int Parse(string enumName, bool ignoreCase)
        {
            int? FoundOrdinal = null;
            foreach (CustomEnumEntry Entry in _IndexedEnum)
            {
                if (string.Compare(Entry.DisplayText, enumName, ignoreCase) == 0)
                    FoundOrdinal = Entry.Ordinal;
                else
                    if (string.Compare(Entry.Name, enumName, ignoreCase) == 0)
                        FoundOrdinal = Entry.Ordinal;
                if (FoundOrdinal != null)
                    break;
            }
            return FoundOrdinal ?? -1;
        }

        #region IEnumerable<CustomEnumEntry> Members

        public IEnumerator<CustomEnumEntry> GetEnumerator()
        {
            return _IndexedEnum.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _IndexedEnum.GetEnumerator();
        }

        #endregion
    }

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 24-Mar-2006 11:39:30   

Could you elaborate a bit about the purpose of this, so I can understand it better and perhaps make changes to v2 to make your life easier? simple_smile

Frans Bouma | Lead developer LLBLGen Pro
jtgooding
User
Posts: 126
Joined: 26-Apr-2004
# Posted on: 24-Mar-2006 15:32:32   

We wrapper the LLBLGen entities into our own business objects, I'm assuming similar to the JCL stuff Omar wrote but not positive since his stuff came out long after I had developed our own internal framework and haven't taken a close look at it, but I'm sure there are some similarities since I use several concepts similar to CSLA (primarily the rule validation system).

So in our business class we have to enumerate every field that we want to run through the validation system which includes all of the fields defined in your enumeration plus additional ones, then we have to provide mapping between them. This prohibits us from using the LLBLGen entities as our base objects (other than the fact that the current validation system is insufficient for our needs).

This type of partial class enumeration allows you to do a two class scenario that uses a single 'enumeration'.

As for the validator class, we have several enhancements that we require.

1) We use a concept similar to CSLA broken rule collection

2) We have multiple validators and associated collections A) Field validator and rule collection, similar to yours but we pass a reference to our manager into the validator. B) Manager Validator for cross entity validation and complex lookups again we pass our manager into it so we can reuse db lookup code C) Delete Validator for delete rules, again we pass in our manager class D) Role Validator to approve or decline modification of fields E) Collection Validator (used in our custom CollectionManager) this holds rules for collections of managers (i.e. a collection of invoice managers and verifying the max amount of all invoices isn't negative etc.)

3) Ability to validate only on save or field by field, some validations are very costly and we have a lot of remote users; so most of the time we only want to validate on a save.

4) CollectionManager is a custom List class, that holds a collection of managers, on save calls the validators on each manager, calls the collection validator, then attempts to update all managers inside a single transaction.

John

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 24-Mar-2006 17:27:52   

Thanks John, interesting simple_smile . I've added you to the v2 discussion forum where I started a thread about validation. Could you check that thread and if you have time, give some feedback in what should be added as well? I'll examine your posts more tomorrow and will get back to you if I have questions simple_smile

Frans Bouma | Lead developer LLBLGen Pro
fmongeau
User
Posts: 1
Joined: 24-Mar-2006
# Posted on: 24-Mar-2006 23:19:05   

Hi Frans. Where is this v2 discussion forum. I'm looking for dates for v2. Got new projects and want to wait for v2 and not 'waste' time with v1. Thanks. BTW: keep up the good work! Fantastic product and world class support! simple_smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 25-Mar-2006 10:04:50   

fmongeau wrote:

Hi Frans. Where is this v2 discussion forum. I'm looking for dates for v2. Got new projects and want to wait for v2 and not 'waste' time with v1. Thanks. BTW: keep up the good work! Fantastic product and world class support! simple_smile

The v2 discussion forum is a hidden forum with a small group of people who give feedback on features implemented. The group is kept small to keep the discussions in control. V2 is going fine, though we need an extra month to get the development finished, so expect a beta at the end of April. We've completed a lot of hte features on our wishlist, though there's still some stuff left which I consider must-have so that's why we take another month. V2 will be backwards compatible for a very large degree with v1, so you can safely start with v1, then migrate your code over to v2 when it arrives, and perhaps spend a few hours or so porting the details but that's about it.

Thanks for the compliments! smile

Frans Bouma | Lead developer LLBLGen Pro