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

Versions: Framework 4.6.1 ; Core 2.0 ; Standard 2.0 .

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

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

In fact, it seems that such shells need a lot more. Of course, they can be created manually. But maybe there is a more productive way to avoid mechanical work?


Explanation of the problem in a rough example, if done manually:

For example, for Core 2.0 , the Dispatcher not implemented.

An abstract shell / interface / facade is made:

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

Further 2 implementations:

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

and

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

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

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

Thus, it turns out a shell that can be used both in Framework's and Core's applications.

The creation of 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 ready-made solutions ...


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

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


The overall task is to transfer the main part of the WPF application to the core for the potential web-client (leaving WPF running), while many of the main framework's .net elements are not translated to the core.

  • It seems to me that we need to look at each such class separately. In the dispatcher it simply does not make sense, because UI in netcore right now isn’t entirely what is coming from where and why are you going to forward it? - Monk
  • @Monk, On the score "what where from where and why you are going to forward": well, maybe someday through sockets in the Asp.Net Core application there will be, for example, forwarding a bar progress change or other UI changes. But it is too early to talk about specifics. For now, just calling Action . And the Dispatcher is just one of many examples .... - Andrey K.
  • Just you write in the post that you are looking for ready-made solutions. But no one needs ready solutions (in the sense that no one writes them) until there is a real problem. From yours in the title - at least the dispatcher and menus are implemented only in one place and there is nowhere else to drag them. Those. they have only one implementation in the current world and there’s no reason to cut the wrapper. What problem do you want to solve? - Monk
  • @Monk - well, not to change the code of the entire application (not enough time and difficult to understand), but simply to replace the class call with the wrapper call, and inside the wrapper so that the class is selected depending on the platform, so that the application logic will work both under the core and under framework - Andrey K.

1 answer 1

At this point, I found, at least, satisfying ways to create shells compatibility. Maybe there are more productive methods.

Thank you Firda from the Czech Republic. Here is his answer

1) In principle, quite simple generic shell

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

1.b) Explanation of the account 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) More advanced version allowing 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 doubles for GUI controls ( Eto.Forms )

(in general, Eto.Forms has a wider application - they are already shells 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 { } } }