Roxy: Strong Types and Method Overloading





5.00/5 (4 votes)
describes Strong Typing and overloading functionality recently added to Roxy.
Introduction
I introduced Roxy as a Proxy Generation and (in the near future also) and IoC Container in Introducing Roxy: Powerful Proxy Generation and IoC Container Package and Separation of Concerns and Smart Mixins with the help of Roxy IoC Container and Code Generator.
Name Roxy is derived from two words - Roslyn (the new MS compiler as a service package) and Proxy.
The main reason behind creating Roxy is to enable better separation of concerns using, what I call, 'smart mixins' - mixins that allow renaming and merging of the events, properties and methods. This feature is described in Separation of Concerns and Smart Mixins with the help of Roxy IoC Container and Code Generator article.
I hope that such mixins will eventually replace the implementation inheritance in software languages. In fact, time permitting, I plan to create my own Roslyn modification that would make such feature part of the expanded C# language.
On top of the smart mixins - Roxy also facilitate creating wrapper/adapters and proxy patterns as well as accessing the non-public functionality of 3rd party packages (something that should be definitely used only by experts). These features are described in Introducing Roxy: Powerful Proxy Generation and IoC Container Package article.
Up to a couple of days ago, the Roxy mixins were name based and correspondingly had weak typing and no method overloading was allowed. Now I've completed LINQ based strongly typed functionality, which also allows overloading. In this article I present the demos that explain this new functionality.
I tried making this article as self contained as possible, but it is still advisable to read the previous two articles on Roxy.
Samples
Code Location
The demo code can be downloaded via the link above the article. Also the code exists as a repository NP.Roxy.StrongTypingDemos on github.
The code depends on NP.Roxy nuget package downloadable from nuget.org and on Roslyn (Microsoft.CodeAnalysis) packages. All the packages should automatically download during the first build as long as you have a working internet connection.
Composite Property Getter Sample
Composite Property Getter sample is located under NP.Roxy.CompositeGetters solution.
This sample shows how to set up a property getter for a composite property - a property that depends on other properties or methods of the same interface.
We use Roxy in order to implement a very simple interface IMyData
:
public interface IMyData { string LastName { get; set; } string FirstName { get; set; } // we create FullName using a lambda expression string FullName { get; } }
Properties LastName
and FirstName
are implemented as simple auto properties (which is a default in Roxy) while the property FullName
is implemented by using a LINQ Expression.
Here is the code of Program.Main
which uses Roxy to generate the type and then tests object of that type:
static void Main(string[] args) { // ITypeConfig is an interface // that can be configured to generate the code // for a specific type. // The Template Argument IMyData specifies // the interface to implement. // SecondArgument NoInterface means // that there are no wrapper objects. ITypeConfig typeConfig = Core.FindOrCreateTypeConfig<IMyData, NoInterface>(); // set up the lambda expression for IMyData.FullName Property // implementation typeConfig.SetPropGetter<IMyData, string> ( // this expression specified the name // of the property to implement (data) => data.FullName, // this is the property implementation expression: // Full (data) => data.LastName + ", " + data.FirstName ); // specify that the type configuration is completed typeConfig.ConfigurationCompleted(); // Get an instance of IMyData generated implementation // type. IMyData myData = Core.GetInstanceOfGeneratedType<IMyData>(); // set FirstName myData.FirstName = "Joe"; // set LastName myData.LastName = "Doe"; // Print FullName Console.WriteLine(myData.FullName); // save the Roxy generated project under GeneratedCode folder (within the // directory that contains the executable) Core.Save("GeneratedCode"); }
The program will print the FullName
property value "Doe, Joe" to the console.
The most important code here is:
// set up the lambda expression for IMyData.FullName Property // implementation typeConfig.SetPropGetter<IMyData, string> ( // this expression specified the name // of the property to implement (data) => data.FullName, // this is the property implementation expression: // Full (data) => data.LastName + ", " + data.FirstName );
The first argument is an expression that specifies the name of the property "FullName" whose getter is being set. The second argument specifies the actual expression that forms the getter.
Important Note I am generating the code and insert it into the generated class based on the LINQ expression - I am not inserting the lambda itself - since i thought that would be too confusing - the class will have some static lambdas but the code for them would be specified in the class builder area. Because of that, the closure will not work - the expression should only consist of the class properties, some static functionality, visible from the class and, perhaps, some constants. This is something I might change in the future - I might provide the actual lambdas for calculation also generating the expression code as a comment for clarity. In that case, the generated code will be more confusing, but the closures will work 100%.
Turning Enumeration into an Interface using Strongly Typed LINQ Expression
In the first Roxy article Introducing Roxy: Powerful Proxy Generation and IoC Container Package, I showed how to turn an enumeration into an interface. The enumeration ProductKind
had an extension class ProductKindExtensions
that was providing static extension methods static string GetDisplayName(this ProductKind productKind)
and static string GetDescription(this ProductKind productKind)
that mapped into the same named methods of the interface.
The strongly typed counterpart of this sample is located under NP.Roxy.StrongTypeEnumTest solution.
Using this, strongly typed LINQ based API we can map those extension methods to properties (not necessarily methods) on the interface.
Here is the code ProductKind
enum and its extension class ProductKindExtensions
:
public enum ProductKind { Grocery, FinancialInstrument, Information } public static class ProductKindExtensions { // returns a displayable short name for the ProductKind public static string GetDisplayName(this ProductKind productKind) { switch (productKind) { case ProductKind.Grocery: return "Grocery"; case ProductKind.FinancialInstrument: return "Financial Instrument"; case ProductKind.Information: return "Information"; } return null; } // returns the full description of the ProductKind // note that the method is private public static string GetDescription(this ProductKind productKind) { switch (productKind) { case ProductKind.Grocery: return "Products you can buy in a grocery store"; case ProductKind.FinancialInstrument: return "Products you can buy on a stock exchange"; case ProductKind.Information: return "Products you can get on the Internet"; } return null; } }
The interface IProduct
that we want to implement contains only two properties that we want to map into the extension methods:
public interface IProduct { string DisplayName { get; } string Description { get; } }
Here is the documented code of Program.Main
:
static void Main(string[] args) { // create the ITypeConfig for implementing the // IProduct interface based on ProductKind enumeration ITypeConfig<SingleWrapperInterface<ProductKind>> adapterTypeConfig = Core.FindOrCreateSingleWrapperTypeConfig<IProduct, ProductKind>(); // set IProduct.DisplayName property to be implemented // as ProductKindExtensions.GetDisplayName() extension method // on the product kind value adapterTypeConfig.SetWrappedPropGetter<IProduct, ProductKind, string> ( prod => prod.DisplayName, prodKind => prodKind.GetDisplayName() ); // set IProduct.Description property to be implemented // by calling ProductKindExtensions.GetDescription() extension method // on the product kind value adapterTypeConfig.SetWrappedPropGetter<IProduct, ProductKind, string> ( prod => prod.Description, prodKind => prodKind.GetDescription() ); // complete configuration and generate the code adapterTypeConfig.ConfigurationCompleted(); // get IProduct for ProductKind.Information enum value IProduct product = Core.GetInstanceOfGeneratedType<IProduct>(null, ProductKind.Information); // write <DisplayName>: <Description> Console.WriteLine($"{product.DisplayName}: {product.Description}"); // save the Roxy generated project under GeneratedCode folder (within the // directory that contains the executable) Core.Save("GeneratedCode"); }
When you run the sample, you'll get the following printed to the console:
Information: Products you can get on the Internet
"Information" is the DisplayName
of the ProductKind.Information
enumeration value and "Products you can get on the Internet" is its Description
.
Strong Typed Method Wrapping
The sample's code is under NP.Roxy.StrongTypeMethodTest solution.
In this sample, we implement IMyData.GetGreeting(...)
method using MyDataImpl.GetGreetingImpl(...)
implementation.
Here is the code for IMyData
interface and the implementing class MyDataImpl
:
public interface IMyData { string FirstName { get; set; } string LastName { get; set; } string GetGreeting(string greetingMessage); } public abstract class MyDataImpl { public abstract string FirstName { get; } public abstract string LastName { get; } // used to implement GetGreetings method of // IMyData interface. the greetingMessage is // followed by first and last names. public string GetGreetingImpl(string greetingMessage) { return $"{greetingMessage} {FirstName} {LastName}!"; } }
We use the wrapper class IWrapper
to define the MyDataImpl
wrapper object:
// defines a wrapper around MyDataImpl // class public interface IWrapper { MyDataImpl TheDataImpl { get; } }
And here is the Program.Main(...)
method:
class Program { static void Main(string[] args) { // saves the generated files in case of a compilation error Core.SetSaveOnErrorPath("GeneratedCode"); // created the ITypeConfig for configuring the IMyData implementation // using IWrapper interface ITypeConfig<IWrapper> typeConfig = Core.FindOrCreateTypeConfig<IMyData, IWrapper>(); // sets the GetGreetings method of IMyData // interface to MydataImplGet.GreetingImpl implementation // the two last type arguments signify the string argument that is // passed to the method and the string output argument. typeConfig.SetReturningMethodMap<IMyData, MyDataImpl, string, string> ( // specifies the interface method to implement (data, inputStr) => data.GetGreeting(inputStr), // specifies the wrapper object that used // to implement the interface method (wrapper) => wrapper.TheDataImpl, // specifies the method implementation (dataImpl, inputStr) => dataImpl.GetGreetingImpl(inputStr) ); // code is generated typeConfig.ConfigurationCompleted(); // we get the instance of the generated class // that implements IMyData interface IMyData myData = Core.GetInstanceOfGeneratedType<IMyData>(); // set the first and last names myData.FirstName = "Joe"; myData.LastName = "Doe"; // get the greeting by calling IMyData.GetGreeting method of // the interface, passing "Hello" string to it string greetingStr = myData.GetGreeting("Hello"); // Prints the resulting string // "Hello Joe Doe!" Console.WriteLine(greetingStr); // saves the generated code in case of successful // completion. Core.Save("GeneratedCode"); } }
Here is the actual method that does the mapping:
// sets the GetGreetings method of IMyData // interface to MydataImplGet.GreetingImpl implementation // the two last type arguments signify the string argument that is // passed to the method and the string output argument. typeConfig.SetReturningMethodMap<IMyData, MyDataImpl, string, string> ( // specifies the interface method to implement (data, inputStr) => data.GetGreeting(inputStr), // specifies the wrapper object that used // to implement the interface method (wrapper) => wrapper.TheDataImpl, // specifies the method implementation (dataImpl, inputStr) => dataImpl.GetGreetingImpl(inputStr) );
Method Overloading
Take a look at NP.Roxy.MethodOverloadingTest solution.
IMyData
interface declares two methods with the same name but different signature IMyData.GetGreeting()
and IMyData.GetGreeting(string greetingMessage)
.
Correspondingly two the implementation class MyDataImpl
defines two implementations of the same name and signature: IMyData.GetGreeting()
and IMyData.GetGreeting(string greetingMessage)
:
public interface IMyData { string FirstName { get; set; } string LastName { get; set; } // demonstrates method overloading string GetGreeting(); // demonstrates method overloading string GetGreeting(string greetingMessage); } public abstract class MyDataImpl { public abstract string FirstName { get; } public abstract string LastName { get; } // demonstrates method overloading public string GetGreeting() => "Hello World!"; // demonstrates method overloading public string GetGreeting(string greetingMessage) { return $"{greetingMessage} {FirstName} {LastName}!"; } }
Here is the documented Program.Main
code:
static void Main(string[] args) { // saves the generated code on compilation error Core.SetSaveOnErrorPath("GeneratedCode"); // creates the TypeConfig object // for IMyData interface to be implemented // using IWrapper.TheDataImpl ITypeConfig<IWrapper> typeConfig = Core.FindOrCreateTypeConfig<IMyData, IWrapper>(); // completes the configuration, // generates the code. typeConfig.ConfigurationCompleted(); // creates the object of generated class that // implements IMyData interface IMyData myData = Core.GetInstanceOfGeneratedType<IMyData>(); // sets first and last name myData.FirstName = "Joe"; myData.LastName = "Doe"; // calls GetGreeting() method string greetingStr1 = myData.GetGreeting(); // writes "Hello World1" Console.WriteLine(greetingStr1); // calls GetGreeting("Hello") method string greetingStr2 = myData.GetGreeting("Hello"); // prints "Hello Joe Doe!" Console.WriteLine(greetingStr2); // saves the generated code in case of // successful completion. Core.Save("GeneratedCode"); }
Both overloaded methods of the interface are getting correct implementations from the overloaded methods of the class. As a result the following will be printed:
Hello World! Hello Joe Doe!
Conclusion
The samples above demonstrate Strong Typing and overloading functionality newly added to NP.Roxy software.