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