如何创建或使用现成的垫片从 .net 框架移植到 .net 核心/标准?

How to create or use ready Shims for porting from .net framework to .net core / standard?

如何为 .net framework 4.6.1 元素创建或使用现成的 Shims 以将它们(从 .net framework 4.6.1)移植到 .net core 2.0 / .net standard 2.0?


一些 class 感兴趣的:,最好为 class 提供垫片,例如:

System.Windows.Threading.Dispatcher

System.ComponentModel.ItemPropertyInfo.Descriptor

甚至

System.Windows.Controls.MenuItem

还有更多...


上下文:

应用程序(代码)并非 100% 组织良好。业务逻辑并非 100% 与 UI 逻辑分离。答案 "do refactoring first" 绝对是一个很好的答案。但就我而言,事情并没有 100% 达到理想状态。


近似示例,尝试手动执行:

System.Windows.Threading.Dispatcher 未在 Core 2.0 中实现。

可以尝试添加:

public enum DispatcherShimPriority
{
    Background
    //...
}

public interface DispaicherShim
{
    void Invoke(Action action, DispatcherShimPriority prio);
    void BeginInvoke(Action action, DispatcherShimPriority, prio);
}

后面是这个接口的两个实现:

public class DispatcherCore: DispaicherShim;

public class DispatcherFramework: DispaicherShim;

后跟一个 class(我们称它为 Shims)在一个多目标项目中:

public static DispaicherShim CreateDispatcher()
{
#if NETCOREAPP2_0
    return new DispatcherCore();
#else
    return new DispatcherFramework();
#endif       
}

结果是 shim,可以在不同的 API 中使用。

这是正确的方法吗?


实际上,创建这样的垫片需要很多日常工作。我觉得这项工作没有必要进行。我觉得这个问题有现成的解决方案...


我知道 Microsoft.Windows.Compatibility 包。当 WPF 涉及许多特定于 wpf 的元素时,这个问题与移植有关。这些元素不在 Microsoft.Windows.Compatibility 包中,但不幸的是,它们在我的程序集中使用,它们是重定向到 .Net Core 2.0 的候选者。我的意思是填充那些 classes,它们不在 Microsoft.Windows.Compatibility 中。

好的,我们有这个 Microsoft.Windows.Compatibility.Shims,但我不确定它对我的情况是否有用;特别是在阅读 following text:

之后

Microsoft.Windows.Compatibility.Shims: This package provides infrastructure services and shouldn't be referenced directly from your code....


Upd:强调最终目标是.net core 2.0

Upd2: 整个任务是将 WPF 应用程序的主要部分移植到 .net core(离开工作 WPF 应用程序)用于潜在的 web-client。主要部分包含 .net framework 未针对 .net core.

实现的元素

Upd3: 关于完整策略的几句话:更完整的策略是共享项目,first approach in this article (#if)。我的策略有两个主要步骤:一个是逐步移植代码,从基础库开始,在顶级库中完成,但大量使用存根和 PlatformNotSupportedExceptions。第二步是从顶级库转移到基础库,通过 .net 核心实现替换存根和异常,按需 (!) - 无需替换所有存根和异常。

Upd4 我们已经将可移植测试与不可移植测试分开(分成两个库)。我们 运行 在移植过程中进行测试非常重要。

以下至少是令人满意的方法:

谢谢Firda from Czech Republic . This is his answer

1) 通用 shim 对我来说就足够了(片段可能有帮助)

public abstract class Shim<TImpl>
{
    internal TImpl It { get; }
    protected Shim(TImpl it) { It = it; }
}

示例:

public class DispatcherPriorityShim : Shim<
#if NETFULL
    DispatcherPriority
#elif NETCORE
    string
#endif
>
{
    public DispatcherPriorityShim(string it)
#if NETFULL
        : base((DispatcherPriority)Enum.Parse(typeof(DispatcherPriority), it))
#elif NETCORE
        : base(it)
#endif
    { }
}

我的 sdk-style .csproj 文件明确了 NETFULLNETCORE:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup><TargetFrameworks>netstandard2.0;netcoreapp2.0;net461</TargetFrameworks></PropertyGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0' OR '$(TargetFramework)' == 'netstandard2.0'">
    <DefineConstants>NETCORE;</DefineConstants></PropertyGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'net461'">
    <DefineConstants>NETFULL;</DefineConstants></PropertyGroup>
</Project>

1.a) Visual Studio snippets

drv

#if NETFULL

#elif NETCORE

#endif

shimenum

namespace PortabilityLibrary.Shims
{
  public class $enumname$Shim : Shim<
#if NETFULL
    $enumname$
#elif NETCORE
    string
#endif
>
  {
        public $enumname$Shim(string it)
#if NETFULL
        : base(($enumname$)Enum.Parse(typeof($enumname$), it))
#elif NETCORE
          : base(it)
#endif
        { }
  }
}

shimsnip

namespace PortabilityLibrary.Shims
{
  public class $classname$Shim : Shim<
#if NETFULL
    $classname$
#elif NETCORE
    $classname$
//NullObject
#endif
>
  {
        public $classname$Shim()
#if NETFULL
        : base(new $classname$())
#elif NETCORE
        : base(new $classname$())
    //: base(new NullObject())
#endif
        {}
  }
}

shimmeth

        public void $methodname$()
        {
#if NETFULL
        It.$methodname$();
#elif NETCORE
        It.$methodname$();
        //throw new ShimException();
#endif
        }

shimprop - 还没有


2) 需要继承的情况。

public interface IShimOne
{
    void MethodOne();
}
public interface IShimTwo: IShimOne
{
    void MethodTwo();
}
#if NETFULL
class One: RealOne, IShimOne {}
class Two: RealTwo, IShimTwo {}
public static class ShimFactory
{
    public static IShimOne CreateOne() { return new One(); }
    public static IShimTwo CreateTwo() { return new Two(); }
}

2.a) 继承对象

public class WrapperOne
{
    protected IShimOne It { get; }
    protected WrapperOne(IShimOne it) { It = it; }
    public WrapperOne() { It = ShimFactory.CreateOne(); }
    public void MethodOne() { It.MethodOne(); }
}
public class WrapperTwo: WrapperOne
{
    protected new IShimTwo It => (IShimTwo)base.It;
    protected WrapperTwo(IShimTwo it): base(it) {}
    public WrapperTwo(): base(ShimFactory.CreateTwo()) {}
    public void MethodTwo() { It.MethodTwo(); }

3) 就绪 "counterparts" GUI 控件 (Eto.Forms)

(实际上,Eto.Forms 有更广泛的应用 - 它们是垫片)

This framework can be used to build applications that run across multiple platforms using their native toolkit, with an easy to use API. This will make your applications look and work as a native application on all platforms, using a single UI codebase...

//Not fully implemented, just showing the idea:

#if NETFULL
using System.Windows.Controls;
#elif NETCORE
using Eto.Forms;
#endif

namespace PortabilityLibrary.Shims
{
    public class MenuItemShim : Shim<
#if NETFULL
    MenuItem
#elif NETCORE
    MenuItem
#endif
    >
    {
        public MenuItemShim(EventHandler<EventArgs> dlg)
#if NETFULL
        : base(new MenuItem(/*not implemented*/))
#elif NETCORE
        : base(new ButtonMenuItem(dlg))
#endif
        { }
    }
}

从 Standard .Net 迁移到 .Net Core 不仅仅是一次升级,考虑到事物的组合方式,您几乎可以称其为向新平台的迁移。迁移到 .Net 核心意味着学习和创建一个可以复制现有代码的新框架。

由于 .Net core 1、1.1、2.0 和 2.1 之间的巨大差异,这些版本的迁移过程发生了很大变化,因此没有一种适合所有人的尺寸 "shim" 并且具有某种包装器否则迁移工具很快就会过时。需要完成迁移代码的工作。

一些核心 OS API 是相似的,但很多框架代码已被移动或更改,因此追逐同类交换可能也很困难。真正值得做一些研发来了解差异在哪里,更不用说使用第 3 方库等了。