-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[C# Feature Request] Hierarchy Data Type #16193
Comments
It seems there are needs both from a structural and design ease (intellisense) for hierarchies. I did see a couple of those items, and it seemed that somewhere in the root of the problem was hierarchies. My thought is that by creating a low level construct we may be able to create a more generalized solution to hierarchy related issues/needs. In some of the comments it kind-of seemed that the root need was a hierarchy, and the proposal was (partially) a way to solve that using current technology - some through unions and some through nestable enums. I think either or both are nice, but a more generalized and independent solution would be better. One of the advantages (and drawbacks) with a new type we get new options (and problems). The case that comes to mind is with an nested-enum approach. Two things I have seen requested are nestable enums, and the ability to declare enums in functions. (With local functions I think there is a stronger case to enable that now). My particular problem with enums is not being able to use them in attributes. Changing enums or the way enums are handled to address these items has many potential side effects. With a new type we as not as constrained by the as-is, though of course the work is much greater. (And it's been a while since we've had one :). In the short term, I think nestable enums would be helpful, though it seems it's not an easy thing to do. I can get around attribute thing pretty easily by passing them as just object, so for my own needs that approach would help. I would like to see a true hierarchal type in 7+1 or +2 though... As for declaring enums in functions, are you aware of an open item for that? I can't find one. With local functions, declaring them at the class level starts removing them pretty far from point of use in things like switches in a local function. If you are not aware of an existing request I'll open one. |
@RandyBuchholz, one nitpicking comment: there are no nested namespaces; only nested namespace definitions.
is exactly the same as:
Help me out here understanding this. in Is |
Shapesshapes: I tagged that as convoluted because it's getting out there a little, and more discussion thoughts then solid approach. But, the Type would be the type of the last node in the sequence. I'll repeat the block
The first line
A way to read this would be, "Hierarchy Shapes is a set of Classes (Shapes The next line
The Square : Rectangle was meant to show that you can use inheritance, but I think that is a bad example. So, for One option is to require all names to be unique in the hierarchy, which I actually prefer initially. Without that, OtherThe { } does two things, so it may not be the best syntax. It is both declaring and initializing. I had it declaring so when you have .xxx below you know that you are elaborating a member defined above. I see that may be murky. Open and ClosedYou could do "open" ( Open - type is hierarchy.
Closed - type is class. (This shows another place you could declare an underlying type - on Hierarchy)
I'm up in the air about mixing types in hierarchies general because of implementation difficulties and confusing the base purpose. Starting off with single-type closed hierarchies would likely be the most practical. I can see something like this in the short term
Types would likely be restricted to built-in types. A question is are branches also values? Or should they be declared specifically.
Namespacenamespace: Nitpicking is good. Yes, this is just a fragment. I'll explain a little. I'm exploring a concept of an MVC framework that exposes actions as "Business Processes". Views are processes, and @models are messages (@model Message). I'm using attribute routing. In the route (IRouteTemplateProvider - [Route(Template=, Name=, Order=)], Template is a "physical address". It is used by the engine for route matching (e.g., controller/action). I'm using Name to represent a "logical address". In this case, a Business Service. The service hierarchy is different than the implementation hierarchy. I need to "publish" that, so routes can be used by their process name (logical) and not physical (route template) at design time. Ideally, I would have a hierarchy type (hierarchy of types declared at design time) to help me. My work around while I play with this is to reserve a namespace "ServiceCatalog", and place my logical routes in there, as types - enums in this case. So, back to why it looks like that. The Catalog has many nodes and branches. By using that format it is easy to see the overall structure. I'm just exploring this concept (Process Controllers) and I don't know yet if it is viable, so I just threw together a cataloging approach. It feels wrong, but I'm just experimenting. In the root of my application I have ServiceCatalog.cs file to "publish" the services.
Each process controller adds its own services using the normal format (or a hybrid).
The ugliness of this is what started me down this path. Any suggestions on better approaches for managing design time hierarchies would be appreciated. |
In the end, all this has to come down to IL. How do you envision the end result? |
I don't know IL internals well enough to give an informed answer. I think at a very low level, a hierarchy is a recursive collection of object. I can get that pretty directly in C# with something like
I can use it like
So it seems the IL supports the concept. It would be up to the plumbing to translate The language might be able to see
I'm getting out of my depths, but those ideas come to mind. |
I think this is a neat idea, there are some cases where we want to reason about the hierarchy, and this could circumvent the need for nested enums / enums inheritance (which is actually more useful than former, as I've realized). I personally find the syntax above problematic for 2 reasons:
Hierarchy<int> ServiceRoutes { Provider1, Provider2, Provider3 }
.Provider1 { Interface1, Interface2 }
.Interface1 { Provider1, Provider2 } // Legal as ServiceRoutes.Provider1.Interface1.Provider1
.Provider2 { Interface1, UniqueName } // I want to define the provider 2 from the top! So, IMO, the syntax hierarchy Shapes {
Rectangle {
Square,
Rectangle
},
Ellipse {
Circle,
Ellipse
}
} is much more natural. It looks like a collection initializer and reveals the structure in proper pieces, like a tree (we may want to collapse parts of the hierarchy in VS, which comes naturaly in this syntax). I think that using the Hierarchy for something that represents constant data is not optimal, we can already use nested classes for that: Hierarchy<string> Menu { "File", "Edit", "Contact Us" }
.[File] { "Save", "Close" }
.[Contact Us] { "Phone", "Email" } we can already do as: class Program {
static void Main(string[] args) {
Console.WriteLine(Menu.File.value);
Console.WriteLine(Menu.File.Save.value);
Console.WriteLine(Menu.ContactUs.Phone.value);
Console.ReadKey();
}
}
public class Menu {
public static string value { get {return "Menu"; } }
public class File {
public static string value { get {return "File"; } }
public class Save {
public static string value { get {return "Save"; } }
}
public class Close {
public static string value { get {return "Close"; } }
}
}
public class Edit {
public static string value { get {return "Edit"; } }
}
public class ContactUs {
public static string value { get {return "Contact Us"; } }
public class Phone {
public static string value { get {return "Phone"; } }
}
public class Email {
public static string value { get {return "E-mail"; } }
}
}
} It's not pretty but we can do it. So, while IMO that would not be the main use case, there is something here that I think can be very significant. What I think Hierarchies are best for would be "custom metadata for custom types". It sounds strange, but even in the current proposition the use of hierarchy is to denote some type of metadata about the types. For example,
all this does is allows us to "dot-to" the proper type and gives us some type of "metadata" that Square is a type of Rectangle, which is a type of Shape. Rectangle and Shape don't do anything in the context of the hierarchy except giving this metadata, they have no data and no methods. However, they could, but separate from the hierarchy data type like I will list below. There could be a shorthand, for example:
would in fact be the same as Also, the quick traversal like Shapes..Square can be pretty neat, but if there are multiple elements with the same name there could be some lack of clarity on the user side (the compiler could just pick the first relevant item in the hierarchy). I think the .. notation would be more appropriate than ..., because it is already intuitive for a "range". I don't mean to hijack this proposal or anything, but the conceptual fit for hierarchies I have in mind is somewhat different. The purpose of Hierarchies could be only to denote the hierarchical relationship between types. Such types would either be custom or "empty" (placeholder) types. Let me expand on that a bit. For each node in the hierarchy it can be (1) "type constant" similar to an enum value, something that is comparable, printable etc. but does not correspond to a proper type any more than an enum value does. Or, it can be (2) type declaration (but not definition), which provides in this context a relation of this type to other nodes in the hierarchy. So, declaration of something in the hierarchy would be like a partial class, but only in cases where there is actual definition of the class, which would be determined by the compiler. So, for example: public hierarchy Menu {
File {
[PrintOption.FullPath("/")] Save,
Close
},
Edit,
ContactUs {
Phone,
[StringValue("E-Mail")] Email
}
}
public class Menu.File.Save { // We need to know that this type is a part of hierarchy, so it must be "fully qualified"
public static void Execute() {
// Does the operation
}
} So, in the example above everything except Menu.File.Save is navigable but has no operations. Side note: if generic attributes where allowed, we could have something like this: The benefits of it is: b) No need for nested enums. Hierarchy is a superset of functionality of a hypothetical nested enum. c) Could give proper type information: d) Less need for enum inheritance. Hierarchy without classes would be a better conceptual fit in some cases. For this to be useful we would need to have hierarchy merging, like so: public partial hierarchy Menu {
File { Save } // In assembly 1, I implement my Save
}
public partial hierarchy Menu {
File { Close } // In assembly 2, e.g. Framework implements Close();
} I don't believe inheritance is possible in this context. Instead, it's a "partial hierarchy". Because of that, all nodes must be treated as leaves, since they may be extended elsewhere. e) Could allow for all members to inherit from the same class: public interface IExecutable {
void Execute;
}
public partial hierarchy Menu : IExecutable {
[DefaultImplementation]
File {
Close
}
} This could mean:
e) Could allow for members of hierarchy type, putting into work the examples from above: public Menu menu = Menu.File.Save; // public field of Menu type, it is a value type (just like an enum).
....
RunMenuItem(menu);
....
public RunMenuItem(IExecutable item) {
item.Execute(); // Legal
} f) When dealing, for example, with abstract factory, like here (https://hfpatternsincsharp.codeplex.com/SourceControl/latest#AbstractFactory/PizzaStore.cs) we have to instantiate based on type, and this requires switch/case on the type representation to instantiate the appropriate type. This kind of work is extension-unfriendly and error prone. We could instead be supplying a hierarchy and instantiating based on hierarchy variable. That code would then look something like: // Before
class CheesePizza : Pizza {
IIngredientFactory ingredientFactory;
public CheesePizza(IIngredientFactory customIngredientFactory) {
this.ingredientFactory = customIngredientFactory;
}
public override void Prepare() {
Console.WriteLine("Making custom cheese pizza.");
this.ingredientFactory.CreateDough();
this.ingredientFactory.CreateSauce();
this.ingredientFactory.CreateCheese();
}
}
public class PortlandPizzaStore : PizzaStore {
// portland-style preparation for all pizzas
protected override Pizza CreatePizza(string type) {
Pizza pizza = null;
IIngredientFactory ingredientFactory =
new PortlandIngredientFactory();
if(type == "Cheese")
pizza = new CheesePizza(ingredientFactory);
else if(type == "Clam")
pizza = new ClamPizza(ingredientFactory);
else if(type == "Suede")
pizza = new ShoePizza(ingredientFactory);
return pizza;
}
}
// After
class PizzaTypeBase : Pizza {
IIngredientFactory ingredientFactory;
public PizzaTypeBase(IIngredientFactory customIngredientFactory) {
this.ingredientFactory = customIngredientFactory;
}
public override void Prepare() {
this.ingredientFactory.CreateDough();
this.ingredientFactory.CreateSauce();
this.ingredientFactory.CreateCheese();
}
}
public hierarchy PizzaType : PizzaTypeBase, new(IIngredientFactory customIngredientFactory) {
Cheese, Clam, Shoe
}
public class PizzaType.Clam { // No need to repeat the inheritance, compiler knows and we know by "fully qualified syntax".
public Clam(IIngredientFactory cif) : base (cif) {} // Don't have to fully qualify in C-tor.
public override void Prepare() {
Console.WriteLine("Making custom cheese pizza.");
base.Prepare();
}
}
// Same for Cheese, Shoe, or for any new type we want to add.
public class PortlandPizzaStore : PizzaStore {
// portland-style preparation for all pizzas
protected override Pizza CreatePizza(PizzaType pizzaType) {
IIngredientFactory ingredientFactory = new PortlandIngredientFactory();
return new pizzaType(ingredientFactory); // We can now new-up a type based on a variable without reflection!
}
} g) Hierarchy can contain different types without any specification or clarification. |
@sirgru, great comments, thanks. I just came up with that syntax to help illustrate some of the concepts and start a discussion. I have no really investment in the actual syntax. I don't think our views are actually different. When you started describing your view you said,
In my view, I abstract that a step further - "The purpose of Hierarchy is only to denote a hierarchical construct." Types - be they nodes types or relation types, are extensions of the basic construct. (I know this is contrary to my original post, which was use-focused.) I think at its core, a hierarchy type is a "locator", and essentially "type-less" (just type Hierarchy). The first purpose is to be able to place something at a location in a hierarchy, provide methods to "know my location", and to be able to navigate to other locations in the hierarchy. Next is the ability to perform logical-type operations (union, exclude, XOR, etc.) on these to combine and extract portions of hierarchies. Treating these as locators removes any implied relationships between nodes. (e.g., type, inheritance). With this in place, the concept is extended to give meaning to the nodes. First, I can see having an underlying/internal type. This would help the engine manipulate the nodes. Following the approach used by From there, user typing/type composition and type relationships (e.g., inheritance) could be added. First is basic node typing or "direct typing". For consistency, I think <> works best here It's at this point in design where questions about things like inheritance need to be resolved. Like, does an untyped node inherit its parent type by default? But, to me these are secondary to having a "type-less hierarchical locator". I think much of the syntax would naturally fall out in the implementation constraints. Based on this "natural" syntax, candy syntax would be added. |
I can't say I really understand this proposal. If you want to continue discussing it, please open a new thread on the csharplang repo. |
Background
There are a few threads that talk about things like nested enums, and other need to represent hierarchal structures. To define a unique spot in a hierarchy we use things like nested namespaces, nested classes, or specialized parser classes. There is no built-in way. On occasion, just to be able to locate a typed-hierarchy object, I've even resorted to things like
so I can happily dot my way around magic strings and keep things unique in a hierarchy. (Please don't judge me...)
There have been discussions related to this like posts
Proposal: Record Enum Types #6739
Proposal: Nested enums #14720
Feature Request: enum to act like types #3704
But I think it may make more sense to create a new type instead of modifying existing types.
Proposal
Create a new datatype "Hierarchy" that represents a hierarchy of Type.
Conceptually
This type could be used in whole or part, to locate types in a hierarchy, or even serve as a meta-type.
It could be used alone, like a nested "enum":
It could also be typed. Each level could represent a different type, but they may need to be restricted to primitives initially.
Just a few different syntax ideas-
They should be usable in most places you can use any other basic type.
Can use in attribute parameters. You can no longer use enums in attribute parameters. If a Hierarchy is a primitive, you should be able. (See next code)
Understand uniqueness. If an element is unique in the hierarchy, it can be addressed directly
I think there is a need to be able to represent hierarchies as true hierarchies, and not have to manually construct nested objects. I don't know what that should actually look like, but I thought I would put out a few ideas.
The text was updated successfully, but these errors were encountered: