GithubHelp home page GithubHelp logo

ifyates / ify.shimr Goto Github PK

View Code? Open in Web Editor NEW
3.0 3.0 0.0 343 KB

Utility for creating a dynamic object facade/proxy to allow for using an object as an interface that it does not explicitly implement

License: MIT License

C# 100.00%

ify.shimr's Introduction

IFYates' GitHub stats
Top Langs

ify.shimr's People

Contributors

ifyates avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar

ify.shimr's Issues

Optional fail on shim create if not completing IDisposable

Is your feature request related to a problem? Please describe.
When creating the shim interface for a target type, it can be easy to miss that the target is disposable and fail to implement IDisposable as part of the structure.
This will remove all of the usual .NET protection and logic for disposables.

Describe the solution you'd like
Add an additional safety check to fail the shim creation if the target type implements IDisposable but the shim structure does not.
Optional, but enabled by default.

Describe alternatives you've considered
It could be a desired behaviour for a shim, but optionality (globally or per shim) seems to cover all scenarios.

Unable to shim method that has been replaced using `new`

Describe the bug
If the class being shimmed defines a "new" method that hides the one attempting to be used in the interface and has a different return type, the shim fails.

Reproducible Example

public abstract class BaseClass {
    public string GetValue() { return "value"; }
}
public class ImplClass : BaseClass {
    new public int GetValue() { return 1; }
}
public interface IShim {
    string GetValue();
}

var obj = new ImplClass();
var shim = obj.Shim<IShim>(); // Will fail
var result = shim.GetValue(); // We expect "value" here

Expected behavior
The method to shim ignores any "new" methods, if these are incompatible.

Environment (please complete the following information):

  • Version v1.6.1

Additional context
The failure is attempting to treat the return type as an auto-shim. This means that issue would be ignored if the return type is an interface, given potentially unexpected results.
It should be possible to shim the "new" method and/or the hidden one.

Allow to shim/override members of facade

Is your feature request related to a problem? Please describe.
Some interface facades can define members that don't exist in the shimmed member. While this can be useful, it would be powerful to be able to shim new functionality in for that member.
Additionally, being able to replace the functionality of the target using the same/similar mechanism.

Describe the solution you'd like
A way to decorate the interface member so that another type can handle the instance call.
Will need to be able to call the original implementation, rather than the shim proxy.

To differentiate and prevent unexpected issues at run-time, allow to choose that will "always override" or "never override", in case an added member is implemented by the target type

Describe alternatives you've considered
Standard extension methods are useful but only allow for extending with new members.

Example
Target:

public class TestClass {
    public void MethodA() { }
    public void MethodB() { }
}
public interface ITestClassFacade {
    void MethodA(); // Calls TestClass.MethodA, as default
    void MethodB(); // Will be overridden to call new implementation which will call the base implementation
    void MethodC(); // Provides new member implementation
}

Proxy implementation example:

public class TestClassProxy {
    readonly TextClass _inst;
    public TestClassProxy(TextClass inst) { _inst = inst; }

    public void MethodA() => _inst.MethodA();

    public void MethodB() {
        // pre-logic
        _inst.MethodB();
        // post-logic
    }

    public void MethodC() {
        // new logic
    }
}

Unable to use ConstructorShim on generic type

Describe the bug
Trying to shim a factory around a generic type fails due to the method not being matched.

Reproducible Example

public interface IActionBlockFactory
{
    [ConstructorShim(typeof(ActionBlock<>))]
    IActionBlock<TInput> StartBlock<TInput>(Action<TInput> action);
}

var factory = ShimBuilder.Create<IActionBlockFactory>();
var instance = factory.StartBlock<ItemType>((item) => { });

Expected behavior
The factory instance can be used to create a new shim of ActionBlock using the given type.

Screenshots / Outputs
Actual output:

System.MissingMemberException: 'Cannot shim System.Threading.Tasks.Dataflow.ActionBlock`1 as IActionBlockFactory; missing method: IActionBlock`1[TInput] StartBlock[TInput](System.Action`1[TInput])'

Environment (please complete the following information):

  • Version: 1.6.4
  • Platform: .NET Core 5

TODO: .NET 5 review + update

  1. Any changes to logic? (Not expecting any)
  2. Update code using new syntax, etc.
  3. Check if net5 happy to use netstandard2.1 library, else: separate package for net5
  4. [unrelated] Drop core package? Provides no value

Cannot auto-shim IEnumerable<> return type

Describe the bug
Because of the IEnumerable-handling logic of auto-shim, attempting to downcast a concrete return type to an IEnumerable<> fails as the logic tries to use the generic argument as the shim type.

Reproducible Example

public sealed class UnmockableType
{
    public List<string> Values { get; }
}

public interface IShimOfType
{
    ICollection<string> Values { get; }
}

var obj = new UnmockableType().Shim<IShimOfType>();

Expected behavior
We can access obj.Values via its interface.

Screenshots / Outputs
Exception is thrown: System.NotSupportedException: Shimmed return type (System.Collections.Generic.ICollection1[[System.String]]) must be an interface, on member: IShimOfType.AcceptThis is because it is checking ifstringis an interface, notICollection`.

Environment (please complete the following information):

  • Version: 2.0.1

Use SourceGenerator to produce shims

Provide a simple mechanism (attribute on interface shim, etc.) that a source generator can use to produce shims at compile time.

Alternative package (IFY.Shimr.Gen)? SGs need to be .NET Standard 2.0 (which runtime Shimr can't use) and potential for different supported functionality between the two.

Prepare shim

Is your feature request related to a problem? Please describe.
Since shim definitions are cached, the first use of shim is slower than others, adding some non-determinism to calls.

Describe the solution you'd like
An explicit way to prepare a shim, e.g.:
static ShimBuilder.PreShim<TObject, TShim>() / static ShimBuilder.PreShim(Type objectType, Type shimType)

Describe alternatives you've considered
In a messy way, you can already achieve this using:

_ = default(TObject).Shim<TShim>();

Additional context
Could potentially be combined with the idea in #10

Hidden property causes ambiguous exception

Describe the bug
If a class hides a similarly named property, the AmbiguousMatchException is thrown.

Reproducible Example

public abstract class BaseClass {
    public string Value { get; set; } = "base";
}
public class ImplClass : BaseClass {
    new public int Value { get; set; } = 1;
}
public interface IShim {
    string Value { get; }
}

var obj = new Issue12Class();
var shim = obj.Shim<IShimIssue12>();
var res = shim.Value; // Expected "base"

Expected behavior
The property with the matching signature is shimmed.

Environment (please complete the following information):

  • Version 1.6.2

Additional context
Issue was hidden until related issue was resolved: #11

Unable to construct generic type using more than one argument

Describe the bug
If the static constructor uses more than one argument, the execution fails with a CLR exception.

Reproducible Example

public class  TestClass<T> {
    public TestClass(int a, int b) { }
}
public interface IFactory {
    [ConstructorShim(typeof(TestClass<>))]
    TestClass<T> New<T>(int a, int b);
}

ShimBuilder.Create<IFactory>().New<string>(1, 2);

Expected behavior
A new instance of the target is created, using the supplied arguments.

Screenshots / Outputs

System.InvalidProgramException: 'Common Language Runtime detected an invalid program.'

Environment (please complete the following information):

  • Version: 1.6.5
  • Platform: .NET 5

Change project name and namespace

To bring in to line with other projects, propose to move to a IFY namespace.

I have long been considering a name-change for the project, and I think this is the opportunity to rename to "Shimr".

So, all existing Shimterface.* namespaces will become IFY.Shimr.*.

The "Standard" can be dropped from the package, as this legacy reference is no longer useful.

Does not support generic method in facade

Describe the bug
If a facade contains a generic method, the shim cannot be compiled.

Reproducible Example

public class TestClass {
    public void Test<T>() {
    }
}

public interface ITestShim {
    void Test<T>();
}

var shim = ShimBuilder.Shim<ITestShim>(new TestClass());

Expected behavior
The returned shim works as expected and shim.Test<string>() executes.

Screenshots / Outputs

System.TypeLoadException: 'Method 'Test' in type 'Shim_TestClass_45861094_ITestShim_23558896' from assembly 'Shimterface.dynamic, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.'

Environment (please complete the following information):

  • Windows
  • 1.4.2 (but affects all)
  • .NET Standard 2.1

Unable to shim RSA.Encrypt

Describe the bug
Creating a shim around the System.Security.Cryptography.RSA classes is throwing MethodAccessException when invoking Encrypt.

Reproducible Example

public interface IRSA : System.IDisposable
{
    void FromXmlString(string xmlString);
    byte[] Encrypt(byte[] data, System.Security.Cryptography.RSAEncryptionPadding padding);
}

var obj = System.Security.Cryptography.RSA.Create();
var shim = obj.Shim<IRSA>();
shim.FromXmlString(/*use own key*/); // Works as expected
var output = shim.Encrypt(data, System.Security.Cryptography.RSAEncryptionPadding.OaepSHA1); // Fails here

Expected behavior
In the example, output would be the same as though calling obj.Encrypt(data, System.Security.Cryptography.RSAEncryptionPadding.OaepSHA1).

Screenshots / Outputs

System.MethodAccessException: Attempt by method 'Shim_RSACng_3175857_IRSA_38222844.Encrypt(Byte[], System.Security.Cryptography.RSAEncryptionPadding)' to access method 'System.Security.Cryptography.RSAImplementation+RSACng.Encrypt(Byte[], System.Security.Cryptography.RSAEncryptionPadding)' failed.

  Stack Trace:โ€‰
Shim_RSACng_3175857_IRSA_38222844.Encrypt(Byte[] , RSAEncryptionPadding )

Environment (please complete the following information):

  • Version: v1.6.7
  • Platform: .NET Core 3.1, .NET 5, .NET 6

Issue with generic constructors when consumed from .NET 6

Describe the bug
Using Shimterface via .NET 6 causes failures with the "generic constructor" usage.

Reproducible Example
Change Shimterface.Standard.Tests to dotnet6.0 and run them.
The 3 generic constructor tests fail.

Expected behavior
All tests pass.

Screenshots / Outputs

Test method Shimterface.Tests.GenericConstructorTests.Can_shim_to_constructor_with_multiple_args threw exception:
System.InvalidProgramException: Common Language Runtime detected an invalid program.

Environment (please complete the following information):

  • Version: 1.6.6
  • Platform: Library is netstandard2.1 but tests are dotnet6.0

Additional context
Changing library to dotnet6.0 does not solve the problem.

Seems to be a regression of #14

Function to check if a shim will succeed

Is your feature request related to a problem? Please describe.
I have a collection of objects and I want to find all objects that can be used as a particular facade.
i.e., get all objects that fulfil an interface.

Describe the solution you'd like
static ShimBuilder.CanShimAs<T>() / static ShimBuilder.CanShimAs(Type t)
extending to
obj.CanShimAs<IMyShim>()

Describe alternatives you've considered
In theory, you could try-catch obj.Shim<IMyShim>(). However, the known bug is that this creates a long-standing error, due to the failed compilation.
Also, try-catch shouldn't be used as logic flow.

Unable to use shim including implemented property

Describe the bug
Exception thrown if implemented property exists in shim.

Reproducible Example

public class TestClass { }
public interface ITestShim
{
    public string ImplementedProperty { get; set; }
}

ITestShim shim = new TestClass().Shim<ITestClass>();

Expected behavior
The created shim has a working ImplementedProperty.

Screenshots / Outputs
Exception:
System.MissingMemberException: Cannot shim TestClass as ITestShim; missing method: System.String get_ImplementedProperty()

Environment (please complete the following information):

  • Version: 1.6.8
  • Platform: .NET Core 3.1, .NET 5, .NET 6

Proxy methods (override or add) do not support parameters

Describe the bug
Adding a proxy methods (override or add) with any parameters after the instance will fail to shim.

Reproducible Example

public class TestClass {
    ...
}

public interface IShim {
    [ShimProxy(typeof(ProxyLogic))] // Add or Override gives same result
    string Method(string arg);
}

public class ProxyLogic {
    public string Method(IShim inst, string arg) {
        return $"[{arg}]";
    }
}

var shim = new TestClass().Shim<IShim>(); // Currently throws exception
var res = shim.Method("test");

Expected behaviour
res should be [test]

Actual behaviour
Exception is thrown.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.