I was thinking about it this morning and the approach that I mentioned in my last post wouldn’t work if you used this from a virtual member and that member was overriden. Here’s the class structure that demonstrates this problem when using yesterday’s approach:
public class Base
{
internal virtual void OnlyCallFromDerivedClasses()
{
Utilities.VerifyCallerIsFamily();
// do something
}
}
public class Derived : Base
{
internal sealed override void OnlyCallFromDerivedClasses()
{
base.OnlyCallFromDerivedClasses();
// do something
}
}
public class NotDerived
{
public void VerifyCannotCallMethod()
{
Derived d = new Derived();
d.OnlyCallFromDerivedClasses();
}
}
Note, for brevity I will not repeat yesterday’s implementation of Utilities.VerifyCallerIsFamily. The problem can be seen if you create an instance of NotDerived and it calls OnlyCallFromDerivedClasses on an instance of B; in that case an exception will not be raised. The reason is that the direct caller of the Utilities.VerifyCallerIsFamily is override of the OnlyCallFromDerivedClasses method in class B so it appears that everything is ok.
Here’s a modified version of Utilities.VerifyCallerIsFamily that gets around this issue:
public static class Utilities
{
[MethodImpl(MethodImplOptions.NoInlining)]
[Conditional(“DEBUG”)]
public static void VerifyCallerIsFamily()
{
// get the method doing the check
StackFrame sfCallee = new StackFrame(1, false);
MethodBase calleeMethod = sfCallee.GetMethod();
int callerIndex = 2;
StackFrame sfCaller = new StackFrame(callerIndex, false);
MethodBase callerMethod = sfCaller.GetMethod();
MethodBase callerPrevious = calleeMethod;
// if the callee – the method checking whose is calling it – is
// virtual then we need to make sure that the caller we are
// verifying is the external method and not the derived classes
// override of the callee method
if (calleeMethod.IsVirtual)
{
while (callerMethod.IsVirtual && callerMethod is MethodInfo)
{
MethodInfo baseMethod = ((MethodInfo)callerMethod).GetBaseDefinition();
// break out once we find a method that is not an override
// of the callee method
if (null == baseMethod ||
baseMethod.MethodHandle != callerPrevious.MethodHandle)
break;
callerPrevious = callerMethod;
callerIndex++;
sfCaller = new StackFrame(callerIndex, false);
callerMethod = sfCaller.GetMethod();
}
}
Debug.Assert(calleeMethod.IsAssembly, “This method is meant to try and implement a scope of ‘Assembly And Family’ so the calling method should be internal.”);
if (false == calleeMethod.DeclaringType.IsAssignableFrom(callerMethod.DeclaringType))
{
// if the caller is a nested type of the callee then
// this is an acceptable call since nested types always have
// access to all the members of the declaring type. this also
// will handle the case where an anonymous method that captures
// a local is used.
Type callerMethodType = callerMethod.DeclaringType;
while (callerMethodType.IsNested)
{
if (calleeMethod.DeclaringType.IsAssignableFrom(callerMethodType.DeclaringType))
return;
// move up the declaring chain
callerMethodType = callerMethodType.DeclaringType;
}
const string Format = “The ‘{0}.{1}’ method is being called from ‘{2}.{3}’. It should only be called by derived types.”;
string message = string.Format(Format,
calleeMethod.DeclaringType.Name,
calleeMethod.Name,
callerMethod.DeclaringType.Name,
callerMethod.Name);
throw new InvalidOperationException(message);
}
}
}
[Note: The section above has been modified from the original posting for this article. The loop dealing with IsNested was added to address the fact that nested classes are supposed to be allowed full access to all members defined by its nesting class.]
Now after we get the caller method, we verify that it is not an override of the callee method. If it is then we continue up the call stack until we hit a method that is not an override of the callee and perform the verification as we had been with that method.
Note, this assumes that the overriden of the member is calling the base. If it is not then it will be up to the override to make the call to VerifyCallerIsFamily.
December 15, 2007 at 5:58 pm |
How does all this work if the “internal AND protected” method is called through a delegate or an anonymous method?
December 16, 2007 at 1:07 am |
Well anonymous methods are just “syntactic sugar”. If you look at the il for a class that uses them, you should see either a static method, an instance method or a nested class. The first is used if the anonymous method doesn’t reference any instance member of the class instance declaring it (and doesn’t capture locals). The second is if the am does reference an instance member. The last is used if you reference/capture a local variable. There’s a great article on am here – http://www.theserverside.net/tt/articles/showarticle.tss?id=AnonymousMethods
So for anonymous methods, the first two would work fine since their declaring type is that of the defining or derived class. The last one would actually be a problem. Actually nested classes should have access to all members of the nesting class (even privates) so I have to make a change in the routine to allow for this. I’ll update the article.
With regards to delegates, I think it depends on how you mean. If you mean that someone calls the internal and protected member within the delegate callback then that method is the caller and the check would be ok. However, if you mean that the delegate’s target is the method that is doing the checking (the callee), then it may not work. If you do a begininvoke on the delegate (which results in it running in a separate thread), then it will result in an exception even if the begininvoke was initiated by the defining or derived class. That class won’t even be in the callstack when the delegate is invoked. However, I believe a direct invocation of the delegate should work. If you could give a specific example or examples, I’ll be sure to look into it more.
December 16, 2007 at 9:48 am |
I was under the misimpression that all anonymous methods were in a compiler-generated nested class, so that’s what I meant. Thanks for pointing out that they are only sometimes in a separate class. I should have specified that in my original comment. Can you check if the type which contains the caller method is nested within the type which contains the callee method using reflection?
With the delegates, I was thinking that someone invoked the “protected AND internal” method directly via the delegate. I hadn’t considered the BeginInvoke problem, but that sounds like a definite no-no for your test method to deal with.
I’m interested to see how you handle these scenarios.
Thanks,
Josh
December 16, 2007 at 10:51 am |
Yes, you can check if its nested (using IsNested) and see who is nesting it (using its DeclaringType). I actually updated the article last night to do just that.
Well I don’t see a way to allow the BeginInvoke version to be done directly. I think if you need to call one of these methods using BeginInvoke that you will need to use a delagate around another method and that method will call the internal and protected method. I checked the delegate call using Invoke on the delegate and it works properly.
December 18, 2007 at 9:14 am |
Nice code. Isn’t it a pain to deal with code formatting in a WordPress blog? Now you see why I take screenshots of my code and just display that image. 😉
December 18, 2007 at 12:42 pm |
Yes I see what you mean. I’m still not sure I’m sold on images but I’ll play around and if I can’t find an acceptable approach I’ll switch over to images as well.
January 1, 2008 at 9:15 am |
COOL Article … I love it….
June 28, 2013 at 3:18 am |
Hi, I do think this is an excellent site. I stumbledupon it 😉 I’m going to come back once again since i have book marked it. Money and freedom is the greatest way to change, may you be rich and continue to guide other people.
July 9, 2013 at 6:41 am |
This design is steller! You definitely know how to keep a reader entertained.
Between your wit and your videos, I was almost moved to
start my own blog (well, almost…HaHa!) Great job.
I really enjoyed what you had to say, and more than that, how you presented it.
Too cool!
July 9, 2013 at 7:52 am |
Wow! Finally I got a blog from where I be able to really obtain valuable facts regarding
my study and knowledge.
July 10, 2013 at 10:54 pm |
Incredible points. Outstanding arguments. Keep up the good effort.
March 2, 2014 at 4:33 am |
True, there are comedians that cuss a lot, but you got to earn that right before you
do that. It is an industry norm that you offer a hotel room for
the comedian at the same time. He represented his homeland in four Olympics and is a three-time indoor world champ in
the 1500 meters.
April 2, 2014 at 6:20 am |
Are you trimming your cat’s claws at least once monthly or obtaining the veterinarian get
it done for you. ncompliment towards the Safari Guide that covers all varieties of
exotic cats. So feeding your cat the correct meals is extremely important to your cat’s general health and nutritional needs.
October 25, 2015 at 2:30 am |
Hire packages vary from one-off, short term and long term.
If yyou have never required scissor lift
hire before, you may not have realized that you actually
have a lot of options. In-ground lifts are the only automotive lifts that reauire this oiil barrier and
it adrds considerably to the overall cost of ownership.
August 2, 2018 at 2:50 pm |
I really like this fishfinder. Exceptional job. All in 1 app.