Claytons Interception
Ayende posted 7 different interception approaches for AOP in .NET. Here's one more...
...well, actually, it's a "cheat's way" to do interception. But, it's has proved to be useful in the real world and it avoids all of the disadvantages mentioned in Ayende's post.
It is "the interception you do, when you're not doing interception"(*). It simply means that you write your property getters and setters in such a way that "interception" is built right into the code. Here's an example from Mindscape LightSpeed:
The Set method can do whatever is required, both before and after it sets the actual value.
Pros:
Simple, understandable, debuggable. Can still use "new" to create objects (unlike some alternatives).
Cons:
Only works for properties. While properties are the main place where interception is required (at least in ORMs and related functionality) some people do want interception on methods too.
Another disadvantage is that you have to put the property name into the setter method. While this is not too bad if your objects are generated (just re-generate if names change) it's still a bit ugly to have string names in the code. I'm particularly uncomfortable with it if anyone is likely to rename the property (using Resharper's Rename feature for instance) while forgetting to also change the string name in the property setter. And, given Murphy's Law and human nature, forgetting probably is likely.
Property Name Solutions
What if you don't like that property name sitting inside the setter methods?
Rocky Lhotka put together a solution to the property naming problem. The solution is based framework code crawling back up the stack to find the property setter. In my own tests, that can be slow and, as Rocky notes, it can be broken by method inlining.
I have put together an alternative solution, which does not suffer from those drawbacks. It lets you write the code above like this (note that the property name string is not inside the setter method any more):
It works by leveraging the fact that the first parameter is a ref parameter. With a bit of managed C++, you can convert that ref param into a managed pointer. Then you can look at how far "into" the object it points, and use that to identify the property. (Technically, it identifies the field. But, if each field is used by one property, then we can easily associate field names with property names.)
Note that the managed C++ code is pure .NET code, marked as "unsafe" so that it can use pointers. It has to be written in C++, not C#, because while C# can use pointers, it cannot create one from a ref parameter.
You can find a full working implementation here. Please note that I am not a C++ developer. This was the first (and probably the last) managed C++ that I have written! While I have undertaken a number of tests to check the validity of this appoach, the fact remains that I'm not experienced in managed C++. Use at your own risk. Please post any questions below.
Update, Feb 08: I've posted an updated, re-written version here.
(*) Austrialian and New Zealand readers may recognise the theme in naming this post. It's based on Claytons, marketed many years ago as "The drink you have when you're not having a drink."
...well, actually, it's a "cheat's way" to do interception. But, it's has proved to be useful in the real world and it avoids all of the disadvantages mentioned in Ayende's post.
It is "the interception you do, when you're not doing interception"(*). It simply means that you write your property getters and setters in such a way that "interception" is built right into the code. Here's an example from Mindscape LightSpeed:
public string DisplayName { get { return _displayName; } set { Set(ref _displayName, value, "DisplayName"); } }
The Set method can do whatever is required, both before and after it sets the actual value.
Pros:
Simple, understandable, debuggable. Can still use "new" to create objects (unlike some alternatives).
Cons:
Only works for properties. While properties are the main place where interception is required (at least in ORMs and related functionality) some people do want interception on methods too.
Another disadvantage is that you have to put the property name into the setter method. While this is not too bad if your objects are generated (just re-generate if names change) it's still a bit ugly to have string names in the code. I'm particularly uncomfortable with it if anyone is likely to rename the property (using Resharper's Rename feature for instance) while forgetting to also change the string name in the property setter. And, given Murphy's Law and human nature, forgetting probably is likely.
Property Name Solutions
What if you don't like that property name sitting inside the setter methods?
Rocky Lhotka put together a solution to the property naming problem. The solution is based framework code crawling back up the stack to find the property setter. In my own tests, that can be slow and, as Rocky notes, it can be broken by method inlining.
I have put together an alternative solution, which does not suffer from those drawbacks. It lets you write the code above like this (note that the property name string is not inside the setter method any more):
public string DisplayName { get { return _displayName; } set { Set(ref _displayName, value); } }
It works by leveraging the fact that the first parameter is a ref parameter. With a bit of managed C++, you can convert that ref param into a managed pointer. Then you can look at how far "into" the object it points, and use that to identify the property. (Technically, it identifies the field. But, if each field is used by one property, then we can easily associate field names with property names.)
Note that the managed C++ code is pure .NET code, marked as "unsafe" so that it can use pointers. It has to be written in C++, not C#, because while C# can use pointers, it cannot create one from a ref parameter.
You can find a full working implementation here. Please note that I am not a C++ developer. This was the first (and probably the last) managed C++ that I have written! While I have undertaken a number of tests to check the validity of this appoach, the fact remains that I'm not experienced in managed C++. Use at your own risk. Please post any questions below.
Update, Feb 08: I've posted an updated, re-written version here.
(*) Austrialian and New Zealand readers may recognise the theme in naming this post. It's based on Claytons, marketed many years ago as "The drink you have when you're not having a drink."
5 Comments:
Hi John,
Hard core! I'll need to take a look. I was using the Rocky approach on LightSpeed but the NoInlining attributes were a mess. It's a shame there isn't a decent out-of-the-box solution for this.
BTW, ReSharper looks inside strings now so it isn't too bad - but still not ideal I agree.
Cheers,
Andrew.
>It's a shame there isn't a decent out-of-the-box solution for this.
Indeed. It's something that Microsoft could theoretically build into the framework somewhere. Have some trusted assembly in the GAC that does the funky C++ stuff...
Why not use reflection to get the property name?
set
{
StackTrace trace = new StackTrace();
string propertyName = trace.GetFrame(0).GetMethod().Name.Substring(4);
_val = value;
NotifyPropertyChanged(propertyName);
}
That would work, and so would MethodBase.GetCurrentMethod().
The problem is that in release builds .NET can "in-line" methods at runtime. That means that the property setter method actually does not exist. (The code that would be in it is just copied into every place that calls it). This is done as part of the JIT runtime compliation from MSIL to native code.
This is the problem that Rocky Lhotka blogged about (above).
Another, less serious problem, is that the reflection based approaches seems to be relatively slow. Does that matter? It all depends on what you are doing. In some cases, the performance difference won't matter.
But the in-lining problem will remain. There is a solution, using attributes to supress the in-lining, but it gets messy (as Andrew mentions above). I would be particularly concerned about it because if anyone forgets the attribute on any property, the problem won't be detected in any testing on Debug builds. It will only happen in Release builds.
For the record, it appears that GetCurrentMethod does not suffer from the in-lining problem, because it is treated specially to ensure that it's caller never gets inlined. I can't see conclusive documentation to support this, but these two pages indicate that it should be the case:
Comment in the Rotor code
Comment on why it's not supported on compact framework (so way to prevent the inlining there)
Finally, last time I tested it's performance (a few years ago now) it came out almost the same as getting the call stack, at bit faster, but not as much faster has I had expected.
Links to this post:
Create a Link
<< Home