为从“此”类型派生的类型提供扩展方法

时间:2018-12-15 20:35:20

标签: c# inheritance extension-methods protected

我已经读过this similar question,但我不希望看到与OP相同的行为,并且我不太了解他,但是我在派生类中使用了受保护成员。

埃里克·利珀特(Eric Lippert)在Why is the 'this' keyword required to call an extension method from within the extended class中写道:

  

...如果您使用的是扩展方法   对于该类型中的某个类型,则可以访问源   码。那么为什么要首先使用扩展方法?

     

...鉴于这两点,负担不再落在语言上   设计师解释为什么不存在该功能。现在落在   您解释为什么应该这样做。功能具有巨大的成本   和他们在一起。

     

...

所以我将尝试解释为什么我会期望一种行为及其用法示例。

功能:

  • 程序员可以在扩展方法内访问this对象的受保护成员。
  • 在扩展方法中使用受保护成员时,只能在从this对象的类型派生的类中使用该方法。
  • 受保护的扩展方法只能使用this参数对象来调用,该参数对象与调用方方法中的this关键字可以访问的对象相同。

现实生活中的使用场景:

我正在根据WPFDesigner_XML示例创建一个Visual Studio自定义编辑器。 目前,我正在尝试使用以下签名在课堂上解决问题:

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

很多方法都在使用这样的服务:

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

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

我喜欢专注于真正重要的事情,因此我编写了辅助方法来简化此类方法。例如:

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

with方法如下:

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

然后我将getServiceQueryType<T>()放在了一个静态类中:

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) },
    };

}

这很好用,但是我也想将with方法放在VSServiceQueryHelper内作为扩展名,因此任何时候只要扩展WindowsPane我就可以将using static com.audionysos.vsix.utils.VSServiceQueryHelper;放在并使用已经实现的with方法。

问题:

我不能使with方法成为扩展,因为它使用的GetService方法是类的基本类型WindowsPane的受保护成员。因此,现在我需要在扩展with的每个类中放置WindowPane实现,这打破了永不重复自己的规则:/

1 个答案:

答案 0 :(得分:1)

一个简单的解决方案是创建一个包含With方法的基类。

如果这太麻烦了,那么您还可以使用Reflection来实现,以从扩展方法中调用GetService方法。实际上,我们可以为其创建一个委托,以确保多次调用的开销最小。

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;
    }
}

我认为您的允许某些扩展方法访问受保护成员的建议不是一个开始。例如,不允许以下内容:

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

但是您会问:“是否不允许从派生类访问基类成员?”不,这实际上不是规则。规则是,只能通过对派生类的引用来访问派生类中的基类成员,而不能直接从任何基类引用中访问基类成员。有关this here的更多详细信息。您的建议等于允许将这种事情用于更大的类方法(即,其他库作者声明为扩展方法的方法)。埃里克·利珀特(Eric Lippert)过去也曾写过关于此问题(herehere)的文章。由于CLR阻止了跨层次结构的调用,所以我不希望像这样的提议很快就会实现。