c# – How to productively create compatibility shims (shims), for .Net Core, Framework, Standard

Question:

How to productively create compatibility shims (shims), for .Net Core , Framework , Standard ?

Versions: Framework 4.6.1 ; Core 2.0 ; Standard 2.0 .

For example, the following 3 things are of interest, for use between .net core , .net standard and .net framework :

System.Windows.Threading.Dispatcher , System.ComponentModel.ItemPropertyInfo.Descriptor , even System.Windows.Controls.MenuItem .

In fact, it looks like a lot more of these shells are needed. Of course, they can be created manually. But maybe there is a more productive way to avoid mechanical work?


Explanation of the task on a rough example, if done manually:

For example, Dispatcher is not implemented for Core 2.0 .

An abstract wrapper / interface / facade is made, :

public enum DispatcherShimPriority
{
    Background
    //...
}

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

Here are 2 implementations:

public class DispatcherCore: DispaicherShim;
//здесь по началу можно просто вызывать Action

and

public class DispatcherFramework: DispaicherShim;
//здесь используется реальный Dispatcher внутри

Next, some kind of multipurpose activator class is made, for example, Shims , in which:

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

Thus, a shell is obtained that can be used in both Framework and Core applications.

Creating such shells requires a lot of mechanical work. Intuitively, it seems to me that it is not necessary to do this work, that there are already ready-made solutions …


About Microsoft.Windows.Compatibility pack in the know. I mean creating wrappers for elements that are not covered by this package.

I heard about Microsoft.Windows.Compatibility.Shims , but I suspect that there are no wrappers for elements that are not covered by the package itself.


The general goal is to translate the main body of a WPF application into core for a potential web client (leaving a working WPF), despite the fact that many elements of the .net framework of the main body are not translated into core.

Answer:

By this point, I've found at least satisfying ways to create compatibility shims. Maybe there are more productive methods.

Thanks to Firda from the Czech Republic. Here is his answer

1) In principle, a simple generic shell is enough

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

EXAMPLE:

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

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 – similar, not done yet

1.b) Explanation of NETCORE and NETFULL

Sdk-style .csproj file to make clear about NETFULL and NETCORE :

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

2) A more advanced version that allows heredity

public interface IShimOne
{
    void MethodOne();
}
public interface IShimTwo: IShimOne
{
    void MethodTwo();
}

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) Objects

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) Ready-made "twins" for GUI controls ( Eto.Forms )

(generally, Eto.Forms has a wider use – they are already wrappers by themselves)

//Не до конца сделано, просто чтобы показать идею:

#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
        { }
    }
}
Scroll to Top