Click here to Skip to main content
15,885,182 members
Articles / Programming Languages / C#

New Roxy Features allowing to Choose Interface Implementation Automatically

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
16 Mar 2018CPOL3 min read 5.4K   23   1  
Describes new important Roxy features for mapping the interface and its implementation wrapper

Introduction

Recently, I introduced Roxy run-time proxy code generator and (would be IoC Container). In particular, here are several articles on Roxy:

As I demonstrate in the above articles (especially the one listed first), Roxy is great for separating concerns in classes that need multiple concerns. It allows assembling implementations of multiple concerns together essentially creating a multiple plugin based inheritance-like functionality.

In this brief article, I present a new important feature that allows to assemble implementations of multiple concerns together without specifying the plugin wrappers when creating a new type. The plugin wrappers, of course, are still there but they are found automagically based on the interfaces we want to implement.

The Code

Code Location

You can download the code from the link above or from GITHUB repository DefaultWrapperTests.

The code consists of two very similar tests: NP.Roxy.DefaultWrappersTest.sln and NP.Roxy.DefaultWrappersTestWithAttrs.sln.

Compiling the Code

The code depends on NP.Roxy nuget package and its dependencies, so you need the internet connection on during the compilation.

NP.Roxy.DefaultWrappersTest

Open and compile NP.Roxy.DefaultWrappersTest.sln console solution.

There is an interface IPersonDataVM defined within PersonDataVM.cs file:

C#
// IPersonDataVM interface
public interface IPersonDataVM
{
    string FirstName { get; set; }
    string LastName { get; set; }

    string FullName { get; }
}  

Its default implementation is PersonDataVM defined next to the interface:

C#
// IPersonDataVM implementation
public class PersonDataVM : IPersonDataVM
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    // FullName is a calculated property
    public string FullName =>
        FirstName + " " + LastName;
}  

There is also a wrapper interface defined at the very bottom of the same (PersonDataVM.cs) file:

C#
// wrapper interface. 
public interface IPersonDataWrapper
{
    PersonDataVM ThePersonWrapper { get; }
}  

The purpose of this interface is to define the plugin (wrapper) implementation.

I am also using ISelectableItem<> interface from NP.Utilities library. This library is loaded via nuget, so the code for this interface and its implementation is not part of this project, but it is part of Roxy code. Here is a GITHUB link to the file containing this interface: SelectableItem.cs and its implementation.

Here is the code for the interface:

C#
public interface ISelectableItem<T>
    where T : ISelectableItem<T>
{
    bool IsSelected { get; set; }

    [EventThisIdx]
    event Action<ISelectableItem<T>> IsSelectedChanged;

    void SelectItem();
}  

Here is the default (and probably the only needed) implementation of the interface:

C#
[Implements(typeof(ISelectableItem<>))]
public class SelectableItem<T> : VMBase, ISelectableItem<T>, INotifyPropertyChanged
    where T : ISelectableItem<T>
{
    bool _isSelected = false;

    [XmlIgnore]
    public bool IsSelected
    {
        get
        {
            return _isSelected;
        }

        set
        {
            if (_isSelected == value)
                return;

            _isSelected = value;

            IsSelectedChanged?.Invoke(this);

            OnPropertyChanged(nameof(IsSelected));
        }
    }

    public event Action<ISelectableItem<T>> IsSelectedChanged;

    public void SelectItem()
    {
        this.IsSelected = true;
    }

    public void ToggleSelection()
    {
        this.IsSelected = !this.IsSelected;
    }
}  

Finally, as in the case of PersonDataVM class, there is also a wrapper interface that defines how the plugin is implemented:

C#
[WrapperInterface(typeof(ISelectableItem<>))]
public interface ISelectableItemWrapper<T>
    where T : ISelectableItem<T>
{
    SelectableItem<T> TheSelectableItem { get; }
}  

At the top of the Program.cs files, the interfaces are merged together into the ISelectablePersonVM interface:

C#
public interface ISelectablePersonVM : IPersonDataVM, ISelectableItem<ISelectablePersonVM>
{

}  

While the implementations are merged using Roxy within the Program.Main method.

Before we create the implementation, however, we specify the mappings within NP.Roxy.Core static class between the interfaces we want to merge and their respective Wrapper implementation interfaces:

C#
// map IPersonDataVM interface into the IPersonDataWrapper interface
// that defines how to implement it
Core.SetWrapperType<IPersonDataVM, IPersonDataWrapper>();

// map ISelectableItem interface into the ISelectableItemWrapper interface
// that defines how to implement it
Core.SetWrapperType(typeof(ISelectableItem<>), typeof(ISelectableItemWrapper<>));  

Now, since the mapping is set, we can create the new type and its instance with a single NP.Roxy.Core call:

C#
// create the instance of a class that implements ISelectablePersonVM
// using automatic wrapper mappings defined above
ISelectablePersonVM personVM = 
    Core.CreateInstanceOfGeneratedType<ISelectablePersonVM>();

The rest of the code Program.Main code tests that the created object correctly implements both IPersonDataVM and ISelectableItem interfaces:

C#
personVM.FirstName = "Joe";
personVM.LastName = "Doe";

// make sure that FullName is "Joe Doe"
Console.WriteLine($"FullName is '{personVM.FullName}'");

// make sure that the IsSelectedChanged event fires. 
personVM.IsSelectedChanged += PersonVM_IsSelectedChanged;

// when IsSelected changes.
personVM.IsSelected = true;

When you run the test, it will print the following to the console:

C#
FullName is 'Joe Doe'
Is Selected Changed  

NP.Roxy.DefaultWrappersWithAttrsTest

DefaultWrappersWithAttrsTest is almost the same as the previous sample, only mappings here are created by class attribute WrapperInterfaceAttribute. Here is the modified code for IPersonDataWrapper:

C#
// wrapper interface. 
[WrapperInterface(typeof(IPersonDataVM))]
public interface IPersonDataWrapper
{
    PersonDataVM ThePersonWrapper { get; }
}  

Note the class attribute. Also, coming back to ISelectableItemWrapper:

C#
[WrapperInterface(typeof(ISelectableItem<>))]
public interface ISelectableItemWrapper<T>
    where T : ISelectableItem<T>
{
    SelectableItem<T> TheSelectableItem { get; }
}  

Note the class attribute also here.

When using class attributes, we can simply call method NP.Roxy.Core.AddTypeAssemblyStatic<T>(); to create the mappings between all the Wrapper interfaces contained in the assembly pointed by generic type argument T and their original interfaces to implement. This is exactly what is done at the top of Program.Main method:

C#
static void Main(string[] args)
{
    // add NP.Utilities assembly 
    Core.AddTypeAssemblyStatic <ISelectableItem<ISelectablePersonVM>> ();
    // add this (NP.Roxy.DefaultWrappersWithAttrsTest) assembly
    Core.AddTypeAssemblyStatic<ISelectablePersonVM>();

    // create the instance of a class that implements ISelectablePersonVM
    // using automatic wrapper mappings defined above
    ISelectablePersonVM personVM = 
        Core.CreateInstanceOfGeneratedType<ISelectablePersonVM>();

    personVM.FirstName = "Joe";
    personVM.LastName = "Doe";

    // make sure that FullName is "Joe Doe"
    Console.WriteLine($"FullName is '{personVM.FullName}'");

    // make sure that the IsSelectedChanged event fires. 
    personVM.IsSelectedChanged += PersonVM_IsSelectedChanged;

    // when IsSelected changes.
    personVM.IsSelected = true;
}

private static void PersonVM_IsSelectedChanged(ISelectableItem<ISelectablePersonVM> obj)
{
    // make sure that the IsSelectedChanged event fires. 
    Console.WriteLine("Is Selected Changed");
}  

Summary

This article talked about a new feature for mapping the interfaces and their implementations while creating the class that contains multiple implementation concerns using Roxy.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect AWebPros
United States United States
I am a software architect and a developer with great passion for new engineering solutions and finding and applying design patterns.

I am passionate about learning new ways of building software and sharing my knowledge with others.

I worked with many various languages including C#, Java and C++.

I fell in love with WPF (and later Silverlight) at first sight. After Microsoft killed Silverlight, I was distraught until I found Avalonia - a great multiplatform package for building UI on Windows, Linux, Mac as well as within browsers (using WASM) and for mobile platforms.

I have my Ph.D. from RPI.

here is my linkedin profile

Comments and Discussions

 
-- There are no messages in this forum --