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; }
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 typeConfig =
Core.FindOrCreateTypeConfig<IMyData, NoInterface>();
typeConfig.SetPropGetter<IMyData, string>
(
(data) => data.FullName,
(data) => data.LastName + ", " + data.FirstName
);
typeConfig.ConfigurationCompleted();
IMyData myData = Core.GetInstanceOfGeneratedType<IMyData>();
myData.FirstName = "Joe";
myData.LastName = "Doe";
Console.WriteLine(myData.FullName);
Core.Save("GeneratedCode");
}
The program will print the FullName
property value "Doe, Joe" to the console.
The most important code here is:
typeConfig.SetPropGetter<IMyData, string>
(
(data) => data.FullName,
(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
{
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;
}
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)
{
ITypeConfig<SingleWrapperInterface<ProductKind>> adapterTypeConfig =
Core.FindOrCreateSingleWrapperTypeConfig<IProduct, ProductKind>();
adapterTypeConfig.SetWrappedPropGetter<IProduct, ProductKind, string>
(
prod => prod.DisplayName,
prodKind => prodKind.GetDisplayName()
);
adapterTypeConfig.SetWrappedPropGetter<IProduct, ProductKind, string>
(
prod => prod.Description,
prodKind => prodKind.GetDescription()
);
adapterTypeConfig.ConfigurationCompleted();
IProduct product = Core.GetInstanceOfGeneratedType<IProduct>(null, ProductKind.Information);
Console.WriteLine($"{product.DisplayName}: {product.Description}");
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; }
public string GetGreetingImpl(string greetingMessage)
{
return $"{greetingMessage} {FirstName} {LastName}!";
}
}
We use the wrapper class IWrapper
to define the MyDataImpl
wrapper object:
public interface IWrapper
{
MyDataImpl TheDataImpl { get; }
}
And here is the Program.Main(...)
method:
class Program
{
static void Main(string[] args)
{
Core.SetSaveOnErrorPath("GeneratedCode");
ITypeConfig<IWrapper> typeConfig = Core.FindOrCreateTypeConfig<IMyData, IWrapper>();
typeConfig.SetReturningMethodMap<IMyData, MyDataImpl, string, string>
(
(data, inputStr) => data.GetGreeting(inputStr),
(wrapper) => wrapper.TheDataImpl,
(dataImpl, inputStr) => dataImpl.GetGreetingImpl(inputStr)
);
typeConfig.ConfigurationCompleted();
IMyData myData = Core.GetInstanceOfGeneratedType<IMyData>();
myData.FirstName = "Joe";
myData.LastName = "Doe";
string greetingStr = myData.GetGreeting("Hello");
Console.WriteLine(greetingStr);
Core.Save("GeneratedCode");
}
}
Here is the actual method that does the mapping:
typeConfig.SetReturningMethodMap<IMyData, MyDataImpl, string, string>
(
(data, inputStr) => data.GetGreeting(inputStr),
(wrapper) => wrapper.TheDataImpl,
(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; }
string GetGreeting();
string GetGreeting(string greetingMessage);
}
public abstract class MyDataImpl
{
public abstract string FirstName { get; }
public abstract string LastName { get; }
public string GetGreeting() => "Hello World!";
public string GetGreeting(string greetingMessage)
{
return $"{greetingMessage} {FirstName} {LastName}!";
}
}
Here is the documented Program.Main
code:
static void Main(string[] args)
{
Core.SetSaveOnErrorPath("GeneratedCode");
ITypeConfig<IWrapper> typeConfig =
Core.FindOrCreateTypeConfig<IMyData, IWrapper>();
typeConfig.ConfigurationCompleted();
IMyData myData = Core.GetInstanceOfGeneratedType<IMyData>();
myData.FirstName = "Joe";
myData.LastName = "Doe";
string greetingStr1 = myData.GetGreeting();
Console.WriteLine(greetingStr1);
string greetingStr2 = myData.GetGreeting("Hello");
Console.WriteLine(greetingStr2);
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.