How do I invoke (and cast) an introduced generically typed field?
Bear with me - there's a lot going on here. Also Discord is funky about styling my code, so I'm handwriting it (pardon any typos). At a high level, let me explain where I'm trying to go and then I'll get into the issue. I want to attach an attribute to a target type that defines a type (TEntity) and another type (TEntityId) so I can use these to introduce some advice later on. I can retrieve those no problem - each is saved as an
INamedType
on the BuildAspect method.
I then want to iterate through most of the properties of the TEntity type itself and introduce at least one field for several of them, but the type of the field depends on the type of the property itself. Again, this is set up and works fine. I save all the references to the introductions in a dictionary keyed by a reproducible mechanism to link the properties with the introduced types. Again, this works fine.
My target type implements a base type which has a bunch of event callbacks in place. What I want to do is replace the last methods in those event callback chains with a method that can do operations against some or all of these introduced fields and this is where I'm having some difficulty.
I can pass along the produced IIntroductionAdviceResult<IField>
via tags, but my problem is that I can't figure out how to assign the appropriate type to it.
Now, typically I'd do something like this:
But the issue here is that the type is only known by the field itself and only as part of this compilation pipeline. As a result, I cannot do the following (or can I?):
I could try using meta
since this is a template, but it's a similar problem - the only thing that knows what type it is is the method advice itself and I can't cast with that.
Is there a helper method I'm missing somewhere to allow invocation of arbitrary typed introduced fields? Or how else might I go about using the adviceField
in this template?
Thank you!59 Replies
I'm not sure I understand the whole dissertation, but in
var myField = adviceField.Declaration.Value
, myField
will be of type adviceField.Declaration.Type
. It's dynamic
only in the template, but the template compiler replaces this by the actual run-time type.
And there is always meta.Cast
if you need it, but in this case it seems redundant.A follow-up question for you: Is there a cast-like method somewhere which I can use in a
[Template]
that allows me to specify a dynamic?
value and an INamedType
or an IType
that will yield an instance that's typed to that at compile time?
Reason being, it's insufficient for me to simply pass, say, a field into an introduced method because I usually need to pass it into something else as an argument. It's great that at compile-time, getting adviceField.Declaration.Value
will type the field correctly, but it's not going to like it if, at design-time, I attempt to pass a dynamic
into something expecting a more specific type. Rather, since I know what type it's going to be (per the IType
from adviceField.Declaration.Type
), it'd be great if there were even just some syntactic sugar I could use that is ignored at compile time but just allows me to use the type as it will eventually be here at dev-time.
Some additional background on where I'm coming from: In my use case, I'm introducing a pile of fields to a class and I'm using the TypeFactory to build their types dynamically based on the Type
constructor arguments of another attribute attached to the class. I want to introduce additional methods that can perform operations against all (or some) of these fields, but operate against all the fields at one time.
Individually, I can get this to work:
and..
Then I can loop through all the introduced fields and introduce a ClearData method for each field (ensuring they have unique names via the IMethodBuilder), save those introduced advices and pass them into an introduced method for the second template where it invokes each of them.
The fields themselves get strongly typed at design-time because I can infer their type based on the enum value, so I can pass them around to other methods and instances freely, then invoke all of them in the ClearAllData
method.
The downside here is that I wind up with a bunch of private methods that should only be exposed to the respective *AllData methods - rather I'd like to reference each of the fields into a single introduced ClearAllData
method, but that's where question #1 comes in.. Each field has a different TDataKey type associated with it. meaning that unlike my first template above, I cannot simply pass in the generic types to the template because they differ on a per-field basis. I'd like to just pass the list of all the IIntroducedAdviceResult<IField> into the method, iterate through the fields, cast each to their compile-time types (but at design-time so I can pass it into various arguments) and then invoke the Clear method on each of their DataOperator
instances, but I cannot do that without some means of casting out from dynamic? to their respective compile-time types.
Thanks!I don't think there is anything in C# that would support that kind of generics. Are the
private
methods a problem because of IntelliSense? If so, would hiding them using [EditorBrowsable(EditorBrowsableState.Never)]
help?
Or, as an alternative, you can generate any code you want using IStatement
s. But that tends to require fairly ugly string
manipulation.It's more that since I intend to mark the class as partial, I would prefer not to pollute the possible methods by having umpteen different methods (number of utility methods times the number of introduced properties) and would instead rather have only a single method for each action. Marking them with that attribute might work - can you point me to an example of how I might append that to an introduced method?
IStatements are a possibility.. I'd have to figure out how to pass around the right types, but that might be a good route too. I'll give that a shot later today.
Whew.. you weren't kidding. These statement blocks are wordy
See - something like that is so neat that I can trivially construct a type with generic type arguments using variables. There are so many places I can drop an INamedType or an IType into to introduce things that it's just such a pity that there's not a mechanism to take a
dynamic
and type it at design time to whatever sort of IType or INamedType I want so I might just keep on using it as the type I know it to be introduced as, even if it's just some sort of sugar that tricks the IDE locally (since it would all type out correctly at compile time once the dynamics were subbed out)
As an aside - if I apply an Indent in the statement builder and then insert a new line, do I have to re-indent or does it maintain the indented level between newlines until I unindent?Well T# is essentially duck typing so you can always use an interface that has the members you want. The target type does not even need to implement the interface.
It's voiding the warranty of course but since there's no warranty 😉
I mean here you can use IDictionary
Actually, marking them with
EditorBrowsable
won't help, because that only works for members in other assemblies.I fear I've done a poor job of explaining the issue I'm experiencing and why I'm having the problem I am here:
I'm trying to build an aspect that will automatically build and maintain secondary indices for any specified type. I have an attribute attached to the target type with two Types provided: one that identifies a specific model (which I intend to build the indexes for) and another that clarifies what the identifier property type is in that model. The target type implements a base class with all the functionality and I'm looking to augment the methods that handle the end result of event chains with all these cross-index updates.
I iterate through the properties on the specified type and, for those types which I intend to support a secondary index for, I then introduce a field creating a new instance of whatever that index type is (again, varies by property type). As such, each of the introduced fields is a different base type with at least one (sometimes two) generic constraints: the type of the property of the model to serve as the key in the secondary index and the type of the identifier (passed in via the attribute). At the moment I have three different kinds of secondary indexes - a dictionary is just the easiest one to use in these demos.
Up to this point, things work fine. Given a
Vehicle
with a boolean value for the property "IsEnabled", a field is created of Dictionary<bool, List<Guid>>
on my target type:
The issue comes in passing multiple fields to an introduced method. If I pass a single field, I can pass it to something like my ClearData
method in the example above and it produces something like the following (names updated):
That's great for a single field because I can pass the generic constraints over and pass them into the (Dictionary<TKeyEntityType, TKeyEntityIdType>)
I'm doing to the field value, get that typed result and use it downstream in the InvertedSecondaryIndexOperator
. What I cannot figure out how to do is pass multiple such fields into an introduced method to do the same thing. For example:
Is there anything at all like that last line possible? To your suggestion, I cannot cast it as a Dictionary<> and just use it because I don't know the generic constraints to apply and one cannot use variables in is
, as
or ()
casts that I'm aware of.The
TypeFactory.CastTo
thing you want is probably meta.Cast
.
There is also the extension method IExpression.CastTo
so you can do field.CastTo(someOtherType).Value
meta.Cast returns a dynamic - without casting to a constant type, I don't know that this moves the needle along much. But as every IField is an IExpression, perhaps I can use it this way alongside the ExpressionBuilder or StatementBuilders.. more experimentation needed
Well, what do you want it to return? I can't be
T
, since the type of a variable can't change with every iteration.Note that it's
dynamic
only in your template code. In your generated code, it won't be dynamic but strongly and statically typed.I guess that's the issue I'm having here conceptualizing how to deal with this. I would like for it to return T, even if it's just syntactical sugaring here in the template code, so I can write the logic I intend to be copied through to the generated code - that's the point of a [Template] right?
Or is the idea that if I write whatever methods I'm passing the value into, so long as it's compatible at generation time, it'll work because at least it'll be substituted in then?
Yes that's it
I just haven't done a whole lot with
dynamic
before - sorry for the slow learning curve on this oneThe template is mostly a syntax generator i.e. almost a text generator
No problem, it's a totally innovative use of
dynamic
. Never seen before 🙂