- Home
- LLBLGen Pro
- Architecture
Enum Replacement
Joined: 26-Apr-2004
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
}
Joined: 26-Apr-2004
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
Joined: 17-Aug-2003
Thanks John, interesting . 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
Joined: 24-Mar-2006
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!
Joined: 17-Aug-2003
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!
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!