如何创建或使用现成的Shims以从.NET Framework迁移到.NET Core / Standard?

18
如何创建或使用准备好的Shims,将.net framework 4.6.1元素从.net framework 4.6.1移植到.net core 2.0/.net standard 2.0

一些有用的类:希望为以下类创建shim:

System.Windows.Threading.Dispatcher



System.ComponentModel.ItemPropertyInfo.Descriptor

甚至

System.Windows.Controls.MenuItem

等等...

背景:

应用程序(代码)组织不是100%良好。业务逻辑与UI逻辑并没有完全分离。答案“先重构”绝对是一个好答案。但在我的情况下,事情并不完全如理想情况下所应该的。

手动尝试的近似示例:

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

可以尝试添加:
public enum DispatcherShimPriority
{
    Background
    //...
}

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

接着是这个接口的两个实现:

public class DispatcherCore: DispaicherShim;

and
public class DispatcherFramework: DispaicherShim;

在一个多目标项目中,接下来是一个类(我们称之为Shims):

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

这个方法是否正确?


实际上,创建这样的shim需要很多例行工作。我有一种感觉,认为这项工作不需要执行。我有一种感觉,这个问题已经有一个现成的解决方案...


我知道Microsoft.Windows.Compatibility包。问题与在涉及许多WPF特定元素的情况下进行移植有关。这些元素不在Microsoft.Windows.Compatibility包中,但遗憾的是,它们被用于我要重新定位到.Net Core 2.0的程序集中。我的意思是shimming那些不在Microsoft.Windows.Compatibility中的类。

好的,我们有了Microsoft.Windows.Compatibility.Shims,但我不确定它在我的情况下是否有用;尤其是在阅读以下文本之后:

Microsoft.Windows.Compatibility.Shims:此软件包提供基础结构服务,不应从您的代码直接引用....


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

更新2:整个任务是将WPF应用程序的主要部分移植到.net core(保留工作中的WPF应用程序),以供潜在的web-client使用。这个主要部分包含.net framework元素,这些元素没有为.net core实现。

更新3:关于完整策略的几句话:更完整的策略是共享项目,在此文章中的第一种方法(#if)。我的策略有两个主要步骤:一个是逐渐移植代码,从基本库开始,最后完成顶级库,并强调存根和PlatformNotSupportedExceptions的密集使用。第二步是从顶级库移动到基本库,通过.net core实现替换存根和异常,按需执行(!)-不需要替换所有存根和异常。

更新4:我们已经将可移植测试与非可移植测试(分成两个库)分开了。在移植过程中运行测试非常重要。


https://codereview.stackexchange.com/questions/203905/creating-a-compatibility-shim-for-exampleclass-to-be-targeted-both-to-net-frame - Andrey K.
1
这是一个非常有趣的问题,但我不确定是否有什么万无一失的解决方案。我以前转换过应用程序,渐进式方法似乎是最好的,采用现有代码并逐步重构和升级它,.NET Core库可以被.NET Framework项目引用(http://www.ben-morris.com/sharing-libraries-between-net-core-and-net-framework-applications/)- 我会逐渐将逻辑移入新的库文件,作为维护和部署的常规策略的一部分。在没有更完整的策略的情况下,Shims对我来说听起来有点离谱。 - Phill
@Phill,谢谢!更完整的策略是共享项目,第一篇文章中的第一种方法。我的策略有两个主要步骤:一是逐步移植代码,从基础库开始,到顶层库结束,但强烈使用存根和PlatformNotSupportedException。第二步是从顶层库移动到基础库,通过.NET Core实现替换存根和异常,按需(!)-无需替换所有存根和异常。 - Andrey K.
午餐后的额外想法,目前项目在测试方面的进展如何?我会考虑边写单元测试边进行重写,以确保行为与原始行为相同。测试对于向用户和业务保证任何更改不会让他们遇到错误系统非常重要;否则,您往往需要更加努力地恢复他们对转换项目的信心。 - Phill
@Phill,没错!我们已经将可移植测试和不可移植测试分开。在移植过程中运行测试非常重要。 - Andrey K.
2个回答

4
从标准的.Net迁移到.Net Core不仅仅是一个升级,考虑到构建方式,你几乎可以称之为迁移至新平台。迁移到.Net Core意味着学习并创建一个新的框架,现有代码可以被复制。
由于.Net Core 1、1.1、2.0和2.1之间存在很大差异,从这些版本进行迁移的过程也发生了很大变化,因此不存在一种“一刀切”的解决方案,拥有某种包装器或迁移工具也会很快过时。需要付出努力来迁移您的代码。
一些核心操作系统API类似,但许多框架代码已经被移动或更改,因此寻找一对一的交换可能也很困难。值得进行一些研究和开发,看看差异在哪里,更不用说使用第三方库等问题了。

没问题,我已经从标准框架转向使用 .Net Core 开发新应用程序了。虽然有些学习上的痛苦,但这必须在某个时候完成,而且我发现自己学到了新东西,并因此使用了更现代的库。 - Mark Redman

0
以下是至少令人满意的方法:
感谢来自捷克共和国的Firda这是他的答案 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风格的.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 代码片段

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) 为 GUI 控件准备“对应物”(Eto.Forms)

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

该框架可用于使用其本地工具包在多个平台上构建应用程序,并提供易于使用的 API。这将使您的应用程序在所有平台上看起来和工作得像本机应用程序,使用单个 UI 代码库...

//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
        { }
    }
}

@Stefan,谢谢!我最初找到了这个看似令人满意的解决方案并接受了它。但后来收到了一个负评。而且,在收到负评之后,我从某个地方听说“发明轮子是不好的”。在那之后开始了第二次悬赏。 - Andrey K.
啊,好的,这很有道理。 - Stefan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接