2

I've read this similar question I don't expect the same behavior as the OP and I don't really understand him but I have a usage for protected members inside the derived classes.

In Why is the 'this' keyword required to call an extension method from within the extended class Eric Lippert wrote:

... If you are in the scenario where you are using an extension method for a type within that type then you do have access to the source code. Why are you using an extension method in the first place then?

... Given those two points, the burden no longer falls on the language designer to explain why the feature does not exist. It now falls on you to explain why it should. Features have enormous costs associated with them.

...

So I will try to explain why I would expect a behavior and it's usage example.

The feature:

  • A programer can access protected member of this object inside extension method.
  • When a protected member is used within extension method, you can only use the method inside classes derived from type of this object.
  • Protected extension method can be called only with this argument object which is the same object that is accessible by this keyword in caller method.

Real life usage scenario:

I'm playing with creating a Visual Studio custom editor based on WPFDesigner_XML example. Currently I'm trying to figure things out in class with following signature:

public sealed class EditorPane : WindowPane, IOleComponent, IVsDeferredDocView, IVsLinkedUndoClient
{...} 

Lot of methods are using services Like this:

void RegisterIndependentView(bool subscribe)
{
    IVsTextManager textManager = (IVsTextManager)GetService(typeof(SVsTextManager));

    if (textManager != null)
    {
        if (subscribe)
        {
            textManager.RegisterIndependentView(this, _textBuffer);
        }
        else
        {
            textManager.UnregisterIndependentView(this, _textBuffer);
        }
    }
}

I like to keep focus on things that actually matter so I wrote helper method to simplify such methods. For example:

private void RegisterIndependentView(bool subscribe) {
    if (with(out IVsTextManager tm)) return;
    if (subscribe) tm.RegisterIndependentView(this, _textBuffer);
    else tm.UnregisterIndependentView(this, _textBuffer);
}

The with method look like this:

private bool with<T>(out T si) {
    si = (T)GetService(getServiceQueryType<T>());
    return si == null ? true : false;
}

And I placed getServiceQueryType<T>() in a static class:

public static class VSServiceQueryHelper {

    public static Type getServiceQueryType<T>() {
        var t = typeof(T);
        if (!serviceQueryTypesMap.ContainsKey(t)) throw new Exception($@"No query type was mapped in ""{nameof(serviceQueryTypesMap)}"" for the ""{t.FullName}"" interface.");
        return serviceQueryTypesMap[t];
    }

    private static Dictionary<Type, Type> serviceQueryTypesMap = new Dictionary<Type, Type>() {
        { typeof(IVsUIShellOpenDocument), typeof(SVsUIShellOpenDocument) },
        { typeof(IVsWindowFrame), typeof(SVsWindowFrame) },
        { typeof(IVsResourceManager), typeof(SVsResourceManager) },
        { typeof(IVsRunningDocumentTable), typeof(SVsRunningDocumentTable) },
        { typeof(IMenuCommandService), typeof(IMenuCommandService) },
        { typeof(IVsTextManager), typeof(SVsTextManager) },
    };
    
}

This works well but I would also like to place the with method inside VSServiceQueryHelper as an extension so any time I would extend WindowsPane i could just place using static com.audionysos.vsix.utils.VSServiceQueryHelper; at the top and use the with method that is already implemented.

The problem:

I can't make with method an extension because the GetService method used by it is a protected member of the WindowsPane which is the base type of my class. So now I need to place with implementation in every class that extends WindowPane and this breaks the rule of never repeat yourself :/

Community
  • 1
  • 1
  • Good point! Well, that would do the job but that's another level of inheritance just for a single method... – Paweł Audionysos Dec 15 '18 at 20:58
  • Are all classes which derive from `WindowPane` your own classes or any 3rd party? – nosale Dec 15 '18 at 20:59
  • No, it's all mine right now. As i said, I'm just playing with an example but I believe there are some other scenarios where it could be useful. – Paweł Audionysos Dec 15 '18 at 21:02
  • 2
    What's the question? This is nice explanation of your use case and thinking but it isn't really a question. – Mike Zboray Dec 15 '18 at 21:09
  • 1
    How about deriving WindowsPaneEx (for lack of a better name) and deriving the subclass from that? – Tom Blodget Dec 15 '18 at 21:10
  • 2
    Adding another level of inheritance may be the best and cleanest way (even when it's only for one method). As far as I know if you absolutely wanna create an extension method you have to use reflection but this will leave a bad taste. – nosale Dec 15 '18 at 21:13
  • @MikeZboray The question is how to provide similar functionality. I also don't see any other solution for this except from creating another base class but I'm not a fan of deep inheritance. – Paweł Audionysos Dec 15 '18 at 21:17
  • 1
    Ok. The history lesson is really irrelevant to solving the problem. If I didn't want to create a base class, I'd go with reflection to invoke GetService on WindowPane from the extension method. – Mike Zboray Dec 15 '18 at 21:31
  • Of course! Why i did't thought about reflection... stupid I :D Will you post an answer or I should do it? – Paweł Audionysos Dec 15 '18 at 21:35
  • Wait... but If i would use reflection the extension method will be available publicly which is easy to say "not ideal" :( – Paweł Audionysos Dec 15 '18 at 21:37
  • I could use `[System.Runtime.CompilerServices.CallerFilePath]` and use Roslyn API to check if the call was from `WindowsPane` and otherwise throw an Exception but I guess that would be an overkill... and still dangerous – Paweł Audionysos Dec 15 '18 at 21:47
  • Who are we trying to protect it from? You can make the extension method internal. – Mike Zboray Dec 15 '18 at 21:49
  • You can make your extension method `internal` or even `private` (when you only use it in the class you declared it) – nosale Dec 15 '18 at 21:50
  • I'm not sure. From myself I guess... I don't want to make it internal because I maybe later I would like to put this in some library that I will reference and making private extension method?? But your are right, Python guys don't event have such things and they live with that so I can just put a comment to use only withing `WindowsPane` classes... – Paweł Audionysos Dec 15 '18 at 21:56
  • I feel like the problem is that this isn't the use case of an extension method; although you can do whatever you want as long as it works. An extension method should not hold ties to a type but extend a type; like Linq does to ```IEnumerable``` ETC. This feels more like you should be customizing you're own fluent API. – Michael Puckett II Dec 15 '18 at 22:28

1 Answers1

1

A simple solution would be to create a base class containing the With method.

If that is too burdensome, then you can also implement this using Reflection to invoke the GetService method from the extension method. In fact, we can create a delegate to it that will ensure there is minimal overhead to invoking With many times.

internal static class WindowPaneExtensions
{
    private static readonly Func<WindowPane, Type, object> WindowPaneGetService = CreateWindowPaneGetService();

    public static bool With<T>(this WindowPane pane, out T service)
    {
        service = (T)WindowPaneGetService(pane, GetServiceQueryType<T>());
        return service != null;
    }

    private static Func<WindowPane, Type, object> CreateWindowPaneGetService()
    {
        var method = typeof(WindowPane).GetMethod("GetService", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(Type) }, null);
        var del = (Func<WindowPane, Type, object>)method.CreateDelegate(typeof(Func<WindowPane, Type, object>));
        return del;
    }
}

I think your proposal to allow certain extension methods access to protected members is a non-starter. For example, the following is not allowed:

public class MyPane : WindowPane
{
    public static void Test(WindowPane p)
    {
         var service = p.GetService(typeof(Service));
         // etc.
    }
}

But you ask, "Isn't it allowed to access base class members from a derived class?" No, that actually isn't the rule. The rule is that you can access base class members from derived class only via a reference to the derived class, not from any base class reference directly. More details on this here. Your proposal amounts to allowing this kind of thing for an even larger class methods (i.e. methods that some other library author declares to be extension methods). Eric Lippert has written about this issue (here and here) in the past as well. Since cross hierarchy calls are blocked by the CLR, I would not expect something like this proposal to get implemented any time soon.

Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
  • OK, I will just wait for Eric Lippert to write how hard it is to implement the feature and then I will accept your answer :) – Paweł Audionysos Dec 15 '18 at 22:03
  • 1
    @PawełAudionysos Your proposal is a non-starter because it is an even more expansive case of what is sometimes called "cross hierarchy calls" (see [this](https://stackoverflow.com/questions/567705/why-cant-i-access-c-sharp-protected-members-except-like-this) for example). These were blocked for security reasons, I believe. – Mike Zboray Dec 15 '18 at 22:12
  • That's nice catch, I've missed this in the feature description. Of course I don't want to be able to call the extension method on any `WindowPane` object within derived class scope. I expect the feature to work only in current instance where method extension `this` could be only the same `this` that you can access from extension method caller. – Paweł Audionysos Dec 15 '18 at 22:41
  • @PawełAudionysos I'm not sure what you want me to say. I don't work on the compiler, but I think they would say: at the end of the day, what does this feature enable you to do? Avoid declaring a base class? Avoid 3 lines code? For a scenario that isn't that common, this sounds like a lot of work for little benefit. – Mike Zboray Dec 15 '18 at 22:59
  • I also don't work on the compiler but believe it's not that horrifying. In this scenario the extension method could be literal copy-paste private version of extension method at compile time and I don't think it would (though it couldn't use private field of the static class that is also easy detectable). I see many usage of this. Classes could be construct from private component modules etc. And what if I create another base class just for this. Export a library and use it, in some project and then I find out someone else created some other great base class for `WindowPane` what shouldIDoThen? – Paweł Audionysos Dec 15 '18 at 23:19
  • @PawełAudionysos You decide whether to take the breaking change by inheriting your class from GreaterWindowPane instead of WindowPane? It's not an intrinsically bad idea; it's just not really consistent with existing features. I'd say your proposal feels more like F# inline function feature. You want the extension method to behave like it was written inline by the caller. Perhaps that it a better way to frame it if you make a language proposal at [csharlplang](https://github.com/dotnet/csharplang). – Mike Zboray Dec 15 '18 at 23:32
  • No, but by extending `GreaterWindowPane` I'm preventing myself from making breaking change in the future... "You want the extension method to behave like it was written inline by the caller." Yes, except that I don't want extension method to have access to private/internal members of `this` type. I will take a look at this, Thanks – Paweł Audionysos Dec 15 '18 at 23:43
  • Is't F# inline function an equivalent of using c# `[MethodImpl(MethodImplOptions.AggressiveInlining)]` attribute? – Paweł Audionysos Dec 16 '18 at 00:05
  • 1
    No. While similar in concept, F# inline is handled by the F# compiler. The attribute you mentioned is a directive to the JIT compiler to strongly suggest inlining in generated machine code. The C# compiler does not do anything similar. – Mike Zboray Dec 16 '18 at 00:14