Closed warriordog closed 1 year ago
Idea that needs some thought.
We can simplify the API and implementation if the semantics of As<T>()
are changed from this:
- If T is a type in the object, then return the object represented as T
- Otherwise, throw an exception
To this:
- If T is a type in the object, then return the object represented as T
- Otherwise, extend the object to include T, then return the object represented as T
This makes a very minimal and consistent API surface, with no runtime exceptions outside of genuine errors. Extending an object is also very natural, especially if the incoming object may or may not already be extended.
❗ Problem: What if the new extension type cannot be constructed? Do we need a new static virtual interface for this? We can't use [ASTypeKey]
since DI is not available to provide cached metadata.
Current thinking is to not support de-extending an object. That opens a whole can of worms and is only relevant in some extreme edge cases. If it really matters, then the user will just need to manually construct a new object.
Another idea - since TypeMap has to be mutable, we embrace that. No-args constructor, and expose an internal Add()
method to add a type. ASTypeConverter
can construct the instances and pass them in sequentially, and TypeMap will link up all the internal references.
Alternative to this above idea:
As
.Add
publicThis leaves the user responsible for creating new extensions, while still being fairly ergonomic. The big challenge is validation. We need to check for and prevent all of these erroneous cases:
Big issue:
❗ This might require user to construct a SubWhatever
class, and that's far from ideal.
If needed, here's a type for the "wrap and extend base type" concept:
// Put this on ASType, then interleave TThis and TBase throughout all types.
public interface IBaseExtender<out TThis, in TBase>
where TBase : ASType
where TThis : TBase, IBaseExtender<TThis, TBase>
{
public static abstract TThis ExtendBase(TBase baseInstance);
}
Another big idea to consider: what if we combined the sub and main types?
Basically, make all of the types wrap a base instance, then there's no need for a sub type. As a bonus, the virtual redirects themselves could be inherited to avoid duplication. The division would happen between constructors instead of at the type level. The existing no-args and type-only constructors can directly construct the base type, and a new "extension" constructor will accept an existing instance to wrap.
ASType would require special handling as it doesn't have a base, but that's fine. Another issue - Required properties are a bit of a pain with this. They work, but you have to include the redundant initializer block in all subtypes.
Actually this might be too insane. I think I'm just implementing Evolutions in C# which is... not what I planned to do with my Saturday morning.
I guess while we're on the topic of high-level refactors, we could also consider dropping the object-oriented model entirely and using composition. Instead of "extends", we "link to". That would really help for edge cases like OrderedCollectionPage
, which is meant to be both a CollectionPage
and an OrderedCollection
.
The downside is that since we don't have inheritance, subtypes can't override behavior. We would also have to hack in the synthetic types, like ASActor
.
As of latest change (LDContexts property), this now depends on #37 and possibly #38.
Completed with #74
Part of #71. Design the API surface considering application, library, and extension viewpoints. Include sample usage for comparison.
Prototype API (completes research, but may be changed later):