|
public enum AnimalKind
{
Unknown = 0x00000000,
DomesticAnimals = 0x00000001,
Dog = 0x00000010,
Dalmatin = 0x00000100,
Greyhound = 0x00000101,
Malamute = 0x00000102,
Terrier = 0x00000103,
Cat = 0x00000011,
TestCat1 = 0x00000110,
TestCat2 = 0x00000111,
WildAnimals = 0x00000002,
Ape = 0x00000020,
Chimpanzee = 0x00000200,
Gorrila = 0x00000201,
Orangutan = 0x00000202,
Test1 = 0x00002020,
Test2 = 0x00002021,
Deer = 0x00000021
}
public static bool IsChild<TType>(TType child, TType parent)
{
DWORD childIndex = (DWORD)Convert.ChangeType(child, typeof(DWORD));
DWORD parentIndex = (DWORD)Convert.ChangeType(parent, typeof(DWORD));
DWORD index = 0;
index = childIndex >> 4;
return (index == parentIndex && childIndex != parentIndex);
}
|
|
|
|
|
Here's my take on the attribute.
namespace PIEBALD.Attributes
{
[System.AttributeUsageAttribute
(
System.AttributeTargets.Field
,
AllowMultiple=false
,
Inherited=false
)]
public sealed class EnumTreeAttribute : System.Attribute
{
private static readonly System.Collections.Generic.Dictionary
<
System.Type
,
object
> masks = new System.Collections.Generic.Dictionary
<
System.Type
,
object
>( 1 ) ;
public static System.Collections.ObjectModel.ReadOnlyCollection<T>
Masks<T>
(
)
{
System.Collections.ObjectModel.ReadOnlyCollection<T> result ;
lock ( masks )
{
System.Type thetype = typeof(T) ;
if ( masks.ContainsKey ( thetype ) )
{
result = (System.Collections.ObjectModel.ReadOnlyCollection<T> ) masks [ thetype ] ;
}
else
{
if ( !thetype.IsEnum )
{
throw ( new System.ArgumentException ( "T must be an Enum" ) ) ;
}
System.Collections.Generic.List<T> temp = new System.Collections.Generic.List<T>() ;
foreach ( System.Reflection.FieldInfo val in thetype.GetFields() )
{
if ( val.GetCustomAttributes
(
typeof(EnumTreeAttribute)
,
false
).Length > 0 )
{
temp.Add ( (T) val.GetValue ( null ) ) ;
}
}
temp.TrimExcess() ;
temp.Sort() ;
masks [ thetype ] = result = temp.AsReadOnly() ;
}
}
}
}
Use it like:
public enum AnimalKind
{
[PIEBALD.Attributes.EnumTreeAttribute()]
ClassMask = 0xF000,
[PIEBALD.Attributes.EnumTreeAttribute()]
SubclassMask = 0x0F00,
[PIEBALD.Attributes.EnumTreeAttribute()]
MemberMask = 0x00FF,
DomesticAnimals = 0x1000,
Dog = 0x1100,
Dalmation = 0x1101,
Greyhound = 0x1102,
Malamute = 0x1103,
Terrier = 0x1104,
Cat = 0x1200,
WildAnimals = 0x2000,
Ape = WildAnimals | 0x0100,
Chimpanzee = Ape | 0x0001,
Gorilla = Ape | 0x0002,
Orangutan = Ape | 0x0003,
Deer = WildAnimals | 0x0200,
MuleDeer = Deer | 0x0001,
WhiteTailed = Deer | 0x0002
}
(I'm still working on the helper class that uses it, I keep changing my mind on the details.)
Edit: Such as making the List a ReadOnlyCollection.
modified on Thursday, April 10, 2008 11:53 PM
|
|
|
|
|
public enum AnimalKind
{
[PIEBALD.Attributes.EnumTreeAttribute()]
ClassMask = 0xF000 ,
[PIEBALD.Attributes.EnumTreeAttribute()]
SubclassMask = ClassMask | 0x0F00 ,
[PIEBALD.Attributes.EnumTreeAttribute()]
MemberMask = SubclassMask | 0x00FF ,
DomesticAnimals = 0x1000 ,
Dog = 0x1100 ,
Dalmation = 0x1101 ,
Greyhound = 0x1102 ,
Malamute = 0x1103 ,
Terrier = 0x1104 ,
Cat = 0x1200 ,
WildAnimals = 0x2000 ,
Ape = WildAnimals | 0x0100 ,
Chimpanzee = Ape | 0x0001 ,
Gorilla = Ape | 0x0002 ,
Orangutan = Ape | 0x0003 ,
Deer = WildAnimals | 0x0200 ,
MuleDeer = Deer | 0x0001 ,
WhiteTailed = Deer | 0x0002
}
|
|
|
|
|
The Attribute probably shouldn't allow negative values.
|
|
|
|
|
I've made the C# 3.0 enabled version. It is basically just a slightly modified conversion. Some suggestions from the local discussion were included.
public static class EnumExtension
{
public static Boolean IsChildOf(this Enum child, Enum parent)
{
foreach (var attribute in child.GetType().GetCustomAttributes(typeof(StructuredAttribute), true))
{
var parentIndex = (int)Convert.ChangeType(parent, typeof(int));
var childIndex = (int)Convert.ChangeType(child, typeof(int));
var index = childIndex / ((StructuredAttribute)attribute).Span;
return (index == parentIndex && childIndex != parentIndex);
}
return false;
}
public static List<TType> GetChildren<TType>(this Enum parent)
{
var result = new List<TType>();
foreach (var enumValue in (TType[])Enum.GetValues(typeof(TType)))
{
var value = (Enum)Convert.ChangeType(enumValue, typeof(TType));
if (IsChild(value, parent)) result.Add(enumValue);
}
return result;
}
}
You can then use it just like:
if (AnimalKind.Dog.IsChildOf(AnimalKind.DomesticAnimals)).. or
List<AnimalKind> list = AnimalKind.DomesticAnimals.GetChildren<AnimalKind>() I guess a more friendly (non-generic) version of the GetChildren can be crafted but..
regards,
Kate
The wisdom is to see things truthfully.
|
|
|
|
|
You're still hitting Reflection to get the Attributes on each call.
This is very costly and unnecessary.
They don't change, the values don't change; read them once and cache them, then only hit the cache.
|
|
|
|
|
Regardless of the active discussion of the technique (I like the overall idea a lot, personally), it took me a careful reading to "get" the meaning of the 100 value in the attribute.
A line or two of explanation might be a good addition. Maybe:
Enums with the Structured attribute can have their numeric values interpreted to get their parent. Some number of elements are reserved for possible children (the parameter to the Structured attribute) and numbers above that represent the parent.
For example, if the size is 10, the 3rd element is still 3, but its children are 3*10+0 to 3*10+9. The children of the second child of 3 is
(3*10+1)*10+0 to (3*10+1)*10+9
\---------/
|
Parent shifted left by the size to reserve
And clarify that in the code. Perhaps something like:
[Structured(100)]
public enum AnimalKind
{
Unknown = 00000,
DomesticAnimals = Unknown + 1,
Dog = DomesticAnimals * 100 + 0,
Dalmatin = Dog * 100 + 0
Greyhound = Dog * 100 + 1,
Malamute = Dog * 100 + 2,
Terrier = Dog * 100 + 3,
Cat = DomesticAnimals * 100 + 1,
WildAnimals = Unknown + 2,
Ape = WildAnimals * 100 + 2,
Chimpanzee = Ape * 100 + 1,
Gorrila = Ape * 100 + 2,
Orangutan = Ape * 100 + 3,
Deer = WildAnimals * 100 + 1
Or even
[Structured(100)]
public enum AnimalKind
{
Unknown = 00000,
DomesticAnimals = Unknown + 1,
DomesticAnimalParent = DomesticAnimal * 100
Dog = DomesticAnimalParent + 0,
DogParent = Dog * 100
Dalmatin = DogParent + 0
Greyhound = DogParent + 1,
Malamute = DogParent + 2,
Terrier = DogParent + 3,
Cat = DomesticAnimalParent + 1,
WildAnimals = Unknown + 2,
WildAnimalParent = WildAnimals * 100
Ape = WildAnimalParent + 2,
ApeParent = Ape * 100,
Chimpanzee = ApeParent + 1,
Gorrila = ApeParent + 2,
Orangutan = ApeParent + 3,
Deer = WildAnimalParent + 1
Also, rename CreateList to something less generic, like ChildrenOf:
foreach (AnimalKind animalKind in EnumUtility.ChildrenOf(parent))
Finally, do you really want IsChild() to return true for enums without the [Structured] attribute? I'd expect an exception, or at least false.
|
|
|
|
|
Hi, good ideas, typically I'm just emitting thoughts I don't care much about the final implementation . I let the reader decide which implementation suits him. Mostly I don't have time to polish them to the extreme knowing that is a waste of my time. Because no matter how sofisticated you'll make it somebody will complain or suggest. On the other hand if I'll post almost the raw version the readers will have the pleasure to optimize it themselves.
[Structured(100)]
public enum AnimalKind
{
Unknown = 00000,
DomesticAnimals = Unknown + 1,
Dog = DomesticAnimals * 100 + 0,
Dalmatin = Dog * 100 + 0
Greyhound = Dog * 100 + 1,
Malamute = Dog * 100 + 2,
Terrier = Dog * 100 + 3,
Cat = DomesticAnimals * 100 + 1,
WildAnimals = Unknown + 2,
Ape = WildAnimals * 100 + 2,
Chimpanzee = Ape * 100 + 1,
Gorrila = Ape * 100 + 2,
Orangutan = Ape * 100 + 3,
Deer = WildAnimals * 100 + 1 Sounds good. But why not even:
public class Config
{
public const Int32 AnimalKindSpan = 100;
public const Int32 OtherEnumSpan = 64;
}
[Structured(Config.AnimalKindSpan)]
public enum AnimalKind
{
Root = 0,
DomesticAnimals = Root + 1,
Dog = DomesticAnimals * Config.AnimalKindSpan + 0,
Dalmatin = Dog * Config.AnimalKindSpan + 0,
Greyhound = Dog * Config.AnimalKindSpan + 1,
Malamute = Dog * Config.AnimalKindSpan + 2,
Terrier = Dog * Config.AnimalKindSpan + 3,
Cat = DomesticAnimals + Config.AnimalKindSpan + 1,
WildAnimals = Root + 2,
Ape = WildAnimals * Config.AnimalKindSpan + 0,
Chimpanzee = Ape * Config.AnimalKindSpan + 0,
Gorrila = Ape * Config.AnimalKindSpan + 1,
Orangutan = Ape * Config.AnimalKindSpan + 2,
Deer = WildAnimals * Config.AnimalKindSpan + 1
}
Too extreme ? My point is that I leave to a programmer to implement it the way he likes. I cannot and don't want to suggest all the possibilities. If you prefer ChildrenOf over CreateList I can not stop you. I just want to share the ideas I stumbled upon and let them inspire the readers to improve them, adapt them or completely dump them. :P
regards,
Kate
The wisdom is to see things truthfully.
modified on Wednesday, April 9, 2008 6:47 AM
|
|
|
|
|
In your Attribute, I suggest that the field and/or property should not be publicly modifiable.
[AttributeUsage(AttributeTargets.Enum)]
public class StructuredAttribute : Attribute
{
public int span;
public int Span
{
get { return span; }
<big>private</big> set { span = value; }
}
public StructuredAttribute(int span)
{
this.span = span;
}
}
Or, my preference is for the field to be public readonly and not have the property.
(Your property doesn't gain you anything anyway.)
Also, your IsChild method calls GetCustomAttributes on each call, this is costly.
And, your CreateList calls GetValues on each call; which isn't as costly, but shouldn't be necessary. Especially considering that it's recursive. Whoops, I read it again; it's not recursive.
I don't know whether or not you have read my article http://www.codeproject.com/KB/cs/EnumTools.aspx[^]
, but some of the techniques there are applicable here.
Anyway, I have roughed out some code that does this, I'll start a new thread for it later...
modified on Sunday, April 6, 2008 11:52 PM
|
|
|
|
|
I didn't really want to tear into this, but...
public static bool IsChild<ttype>(TType child, TType parent) <big></big>
{
Type enumType = typeof(TType); <big></big>
Object[] attributeList = enumType.GetCustomAttributes(typeof(StructuredAttribute), true);
if (attributeList.Length > 0)
{
StructuredAttribute attribute = (StructuredAttribute)attributeList[0];
int span = attribute.Span; <big></big>
int parentIndex = (int)Convert.ChangeType(parent, typeof(int));
int childIndex = (int)Convert.ChangeType(child, typeof(int));
int index = childIndex / span; <big></big>
return (index == parentIndex && childIndex != parentIndex);
}
return true;
}
And I would replace
Object[] attributeList = enumType.GetCustomAttributes(typeof(StructuredAttribute), true);
if (attributeList.Length > 0)
{
StructuredAttribute attribute = (StructuredAttribute)attributeList[0];
with
foreach
(
StructuredAttribute attribute
in
typeof(TType).GetCustomAttributes(typeof(StructuredAttribute), true)
)
{
(Yeah, I know there'll be at most one instance of the attribute.)
|
|
|
|
|
PIEBALDconsult wrote: In your Attribute, I suggest that the field and/or property should not be publicly modifiable.
I missed that. I've got much bigger class EnumUtility I've just stripped away all the code non-concerned in the article. So the compromise:
private int span = 10;
public int Span
{
get { return span; }
} PIEBALDconsult wrote:
public static bool IsChild<ttype>(TType child, TType parent)
{
Type enumType = typeof(TType);
Object[] attributeList = enumType.GetCustomAttributes(typeof(StructuredAttribute), true);
if (attributeList.Length > 0)
{
StructuredAttribute attribute = (StructuredAttribute)attributeList[0];
int span = attribute.Span;
int parentIndex = (int)Convert.ChangeType(parent, typeof(int));
int childIndex = (int)Convert.ChangeType(child, typeof(int));
int index = childIndex / span;
return (index == parentIndex && childIndex != parentIndex);
}
return true;
}</ttype>
Where if ttype used?: There's no constraint for Enum (it is not possible by C# design) moreover the Attribute is enum only so it ensures that TType will be Enum because otherwise the GetCustomAttributes will be zero.
This is needless: This is my coding pattern of choice (substition pattern) it is IMO more readable then putting altogether (cumulative pattern). Consider this :
return (((int)Convert.ChangeType(child, typeof(int)) / attribute.Span) ==<br> ((int)Convert.ChangeType(parent, typeof(int)) &&<br> ((int)Convert.ChangeType(child, typeof(int) != (int)Convert.ChangeType(parent, typeof(int));</br></br> PIEBALDconsult wrote:
foreach (StructuredAttribute attribute in typeof(TType).GetCustomAttributes(typeof(StructuredAttribute), true))
Fine with me.
PIEBALDconsult wrote: And, your CreateList calls GetValues on each call; which isn't as costly, but shouldn't be necessary.
I guess there should be some common private class determining the paternity. Such as bool (TType child, TType parent, int span)
I'll hear more of your comments and after that I'll modify the article accordingly.
regards,
Kate
The wisdom is to see things truthfully.
|
|
|
|
|
Smart K8 wrote: I've just stripped away all the code non-concerned in the article
And didn't test it?
I meant, in the line:
public static bool IsChild<ttype>(TType child, TType parent)
where is ttype used? Is it supposed to be TType and you didn't capitalize it properly?
Smart K8 wrote: There's no constraint for Enum
Yeah, and there should be. Stupid Microsoft
Anyway, I spent another whole day today tweaking my implementation of this.
|
|
|
|
|
PIEBALDconsult wrote: Smart K8 wrote:
I've just stripped away all the code non-concerned in the article
And didn't test it?
Sure I did.
PIEBALDconsult wrote:
I meant, in the line:
public static bool IsChild<ttype>(TType child, TType parent)
where is ttype used? Is it supposed to be TType and you didn't capitalize it properly?
No, I'm just struggling with the codeproject's < and > a bit.
The wisdom is to see things truthfully.
|
|
|
|
|
Why did you choose to shift the higher-order sub-values up?
I would leave them in place (at the high-end), leave low-order fields as 0 to indicate "class", and add (OR) detail as I go.
The "class" parts could also be at the low-end, but I think high-end is clearer.
(Hexadecimal literals would make it easier too. They following doesn't work correctly, but should give a decent conceptual view.)
public enum AnimalKind
{
Unknown = 00000,
Class = 90000,
Subclass = 90900,
DomesticAnimals = 10000,
Dog = 10100,
Dalmatin = 10101,
Greyhound = 10102,
Malamute = 10103,
Terrier = 10104,
Cat = 10200,
WildAnimals = 20000,
Ape = WildAnimals + 100,
Chimpanzee = 20101,
Gorilla = 20102,
Orangutan = 20103,
Deer = WildAnimals + 200
}
That way bitwise operations can be used (I hope I get this right):
if ( ( AnimalKind.Greyhound & AnimalKind.Subclass ) == AnimalKind.Dog )
|
|
|
|
|
This is what I mean:
namespace Template
{
public partial class Template
{
public enum AnimalKind
{
Unknown = 0x000000,
Class = 0xFF0000,
Subclass = 0xFFFF00,
DomesticAnimals = 0x010000,
Dog = 0x010100,
Dalmatin = 0x010101,
Greyhound = 0x010102,
Malamute = 0x010103,
Terrier = 0x010104,
Cat = 0x010200,
WildAnimals = 0x020000,
Ape = WildAnimals | 0x0100,
Chimpanzee = Ape | 0x01,
Gorilla = Ape | 0x02,
Orangutan = Ape | 0x03,
Deer = WildAnimals | 0x0200
}
public static void
Main
(
string[] args
)
{
AnimalKind a = AnimalKind.Unknown ;
if ( ( args.Length > 0 ) && System.Enum.IsDefined ( typeof(AnimalKind) , args [ 0 ] ) )
{
a = (AnimalKind) System.Enum.Parse ( typeof(AnimalKind) , args [ 0 ] ) ;
}
System.Console.WriteLine
(
"{0} {1:X06} {2} {3}"
,
a
,
(int) a
,
a & AnimalKind.Class
,
a & AnimalKind.Subclass
) ;
switch ( a & AnimalKind.Subclass )
{
case AnimalKind.Dog :
System.Console.WriteLine ( "Arf" ) ;
break ;
case AnimalKind.Cat :
System.Console.WriteLine ( "Meow" ) ;
break ;
}
}
}
}
|
|
|
|
|
Hi.. What you are describing is simple the [Flags] attribute.
The point is in the combination of scalability (branch count/level count) and the ability to list all the children because of that simple fact. Consider this question:
How do you construct a tree with three (or more) levels (A->AB->ABC) and then reconstruct it again to List<TEnumType> GetList<TEnumType>(TEnumType Parent) ?
// Why did you choose to shift the higher-order sub-values up?
Because of the scalability. It seems more natural that a tree starts small. Check this rephrase:
[Structured(100)]
public enum AnimalKind
{
Unknown = 00000,
DomesticAnimals = 1,
Dog = 100,
Dalmatin = 10000,
Greyhound = 10001,
Malamute = 10002,
Terrier = 10003,
Cat = 101,
WildAnimals = 2,
Ape = 200,
Chimpanzee = 20000,
Gorrila = 20001,
Orangutan = 20002,
Deer = 201
}
Do you see the logic now ?
regards,
Kate
The wisdom is to see things truthfully.
The wisdom is to see things truthfully.
|
|
|
|
|
Smart K8 wrote: What you are describing is simple the [Flags] attribute.
Not at all.
Smart K8 wrote: Do you see the logic now ?
Just as I saw it before, but I don't agree that it's the best course of action.
|
|
|
|
|
Sorry, I got confused with another thread.. I guess I'm getting tired.. It's 23:37 here. I'm going to sleep. I checked your solution and this is ofcourse possible but consider the scenario where the Span (as I call it) won't be 0xFF but something lower than that (for example 0xC) because the complexity of the tree won't be sufficient to hold the number anymore.
Class = 0xFF000000,
Subclass = 0xFFFF0000,
SubSubClass = 0xFFFFFF00,
SubSubSubClass = 0xFFFFFFFF,
SubSubSubSubClass = Hello problems!
That's where the (for example) [Structured(0xC)] takes control and also when Natural numbers looks a bit more normal.
regards,
Kate
The wisdom is to see things truthfully.
|
|
|
|
|
Smart K8 wrote: consider the scenario where the Span...
I was simply following your lead of using two digits. But what if you want different spans at different levels? For instance one-digit at the top-most level and two-digits at the next level down?
And, no, I haven't worked that out yet either, but I expect yout Attribute-based solution will be the way to go.
I also acknowledge that you were after a solution that wasn't tied to a particular enum. We'll get there, baby steps.
Smart K8 wrote: Natural numbers looks a bit more normal
You're saying my numbers are unnatural?
Well, the great whole purpose of enums is to hide the actual values anyway.
|
|
|
|
|
You're saying my numbers are unnatural?
IMHO.. in the end it is not important if you choose decimals, hexadecimals or any other representation of the value. I guess it's up to reader to make it suitable. I just thought that using decimals numbers as an example will be understable to a more general public.
And, no, I haven't worked that out yet either, but I expect yout Attribute-based solution will be the way to go.
I also acknowledge that you were after a solution that wasn't tied to a particular enum.
Exactly, prior to posting the article (or even thinking about the solution itself) I was considering all the ways possible to achieve that with either standard enumerations or any other easy way. It was expected that the consensus that this solution is not reinventing the wheel (as leppie pointed out) is first to be made.
The Structured attribute is there only to normalize all the possible granulations of enumerations so then one method can be used to reconstruct the tree + some value is ofcourse in the methods themselves.
We'll get there, baby steps.
Sure and the reasonable compromise of syntax is what I'm after too. I'm glad for every suggestion and I guess I'm willing to modify the article when the best solution is worked out.
regards,
Kate
The wisdom is to see things truthfully.
modified on Sunday, April 6, 2008 3:23 AM
|
|
|
|
|
And thinking bit-wise allows more granularity of the class sizes. Yours only allows 10, 100, 1000, etc. members per class; but mine allows 3, 7, 15, 31, etc.
|
|
|
|
|
What you are looking for is bit masks, and that is built into enum in C# (and many other languages) already.
[edit] you article is by no means bad [edit]
|
|
|
|
|
Hi, I'm aware of the FlagsAttribute but if you give it another round I hope you'll notice the slight improvements which are important in this case.
regards,
Kate
|
|
|
|
|
Sorry I dont see it You can do the same with enums. You dont even need the Flags attribute. Here is a number hierarchy:
<font color="Blue">enum</font> <font color="Teal">NumberClass</font>
<font color="DarkBlue">{</font>
Complex <font color="DarkBlue">=</font> <font color="Red">1</font><font color="DarkBlue">,</font>
Real <font color="DarkBlue">=</font> <font color="Red">2</font> <font color="DarkBlue">|</font> Complex<font color="DarkBlue">,</font>
Rational <font color="DarkBlue">=</font> <font color="Red">4</font> <font color="DarkBlue">|</font> Real <font color="DarkBlue">,</font>
BigInteger <font color="DarkBlue">=</font> <font color="Red">8</font> <font color="DarkBlue">|</font> Rational<font color="DarkBlue">,</font>
Integer <font color="DarkBlue">=</font> <font color="Red">16</font> <font color="DarkBlue">|</font> BigInteger<font color="DarkBlue">,</font>
NotANumber <font color="DarkBlue">=</font> <font color="Red">0</font>
<font color="DarkBlue">}</font>
|
|
|
|
|
Ok.. So how about a second branch in the same enum ?
regards,
Kate
The wisdom is to see things truthfully.
|
|
|
|
|