Internal And Protected Virtual

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.

15 Responses to “Internal And Protected Virtual”

  1. Josh Smith Says:

    How does all this work if the “internal AND protected” method is called through a delegate or an anonymous method?

  2. agsmith Says:

    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.

  3. Josh Smith Says:

    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

  4. agsmith Says:

    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.

  5. Josh Smith Says:

    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. 😉

  6. agsmith Says:

    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.

  7. marlongrech Says:

    COOL Article … I love it….

  8. preschool graduation invitation wording Says:

    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.

  9. real time financial news Says:

    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!

  10. finance help Says:

    Wow! Finally I got a blog from where I be able to really obtain valuable facts regarding
    my study and knowledge.

  11. engagement party invitations wording Says:

    Incredible points. Outstanding arguments. Keep up the good effort.

  12. Jimmie Says:

    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.

  13. Www.feedbigcats.co.uk Says:

    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.

  14. Jody Says:

    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.

  15. humminbird 570 di Says:

    I really like this fishfinder. Exceptional job. All in 1 app.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s


%d bloggers like this: