Let's assume that I have this scenario: I have got 2 repositories of information, and I want to access both, but it would be nice to leave the task of deciding which repo to use to common class.
The goal is to accomplish this with something similar to the code I've wrote below, but this sounds pretty bad:
where TOnline : class
where TOffline : class
where TContract : class
Sure I can ommit that, but bassically what I'm asking is what to do in order to stop using reflection and go typed. Maybe any design-pattern recomendation?
Code (if you copy/paste this on a console app replacing the Program class you should be able to run the example)
using CustomerDispatcher = DispatcherProxy<CustomerOnline, CustomerOffline, ICustomer>;
public interface ICustomer
{
string Get(int id);
}
public class CustomerOnline : ICustomer
{
public string Get(int id)
{
// Get From intranet DB
return "From DB";
}
}
public class CustomerOffline : ICustomer
{
public string Get(int id)
{
// Get From local storage
return "From local storage";
}
}
public class DispatcherProxy<TOnline, TOffline, TContract>
where TOnline : class
where TOffline : class
where TContract : class
{
public TContract Instance { get; set; }
public bool IsConnected { get; set; }
public DispatcherProxy()
{
// Asume that I check if it's connected or not
if (this.IsConnected)
this.Instance = (TContract)Activator.CreateInstance(typeof(TOnline));
else
this.Instance = (TContract)Activator.CreateInstance(typeof(TOffline));
}
}
class Program
{
static void Main(string[] args)
{
var customerDispatcher = new CustomerDispatcher();
Console.WriteLine("Result: " + customerDispatcher.Instance.Get(1));
Console.Read();
}
}
Thanks in advance!
You can add the new() constraint:
public class DispatcherProxy<TOnline, TOffline, TContract>
where TOnline : class, new()
where TOffline : class, new()
where TContract : class //isn't TContract an interface?
{
public TContract Instance { get; set; }
public bool IsConnected { get; set; }
public DispatcherProxy()
{
// Asume that I check if it's connected or not
if (this.IsConnected)
this.Instance = new TOnline() as TContract;
else
this.Instance = new TOffline() as TContract;
}
}
In case any of you are interested, I had to change the way I did this because it was checking connection at Constructor Level, and I needed that check at Operation Level.
using System;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace ConsoleApplication1
{
public enum ConnectionStatus
{
Online,
Offline,
System // System checks connectivity
}
public static class Connectivity
{
private static ConnectionStatus ConnectionStatus = ConnectionStatus.Offline;
public static void ForceConnectionStatus(ConnectionStatus connectionStatus)
{
ConnectionStatus = connectionStatus;
}
public static bool IsConnected()
{
switch (ConnectionStatus)
{
case ConnectionStatus.Online:
return true;
case ConnectionStatus.Offline:
return false;
case ConnectionStatus.System:
return CheckConnection();
}
return false;
}
private static bool CheckConnection()
{
return true;
}
}
public class Unity
{
public static IUnityContainer Container;
public static void Initialize()
{
Container = new UnityContainer();
Container.AddNewExtension<Interception>();
Container.RegisterType<ILogger, OnlineLogger>();
Container.Configure<Interception>().SetInterceptorFor<ILogger>(new InterfaceInterceptor());
}
}
class Program
{
static void Main(string[] args)
{
Unity.Initialize();
var r = new Router<ILogger, OnlineLogger, OnlineLogger>();
Connectivity.ForceConnectionStatus(ConnectionStatus.Offline);
Console.WriteLine("Calling Online, will attend offline: ");
r.Logger.Write("Used offline.");
Connectivity.ForceConnectionStatus(ConnectionStatus.Online);
Console.WriteLine("Calling Online, will attend online: ");
r.Logger.Write("Used Online. Clap Clap Clap.");
Console.ReadKey();
}
}
public class Router<TContract, TOnline, TOffline>
where TOnline : TContract
where TOffline : TContract
{
public TContract Logger;
public Router()
{
Logger = Unity.Container.Resolve<TContract>();
}
}
public interface IOnline
{
IOffline Offline { get; set; }
}
public interface IOffline
{
}
public interface ILogger
{
[Test()]
void Write(string message);
}
public class OnlineLogger : ILogger, IOnline
{
public IOffline Offline { get; set; }
public OnlineLogger()
{
this.Offline = new OfflineLogger();
}
public void Write(string message)
{
Console.WriteLine("Online Logger: " + message);
}
}
public class OfflineLogger : ILogger, IOffline
{
public IOnline Online { get; set; }
public void Write(string message)
{
Console.WriteLine("Offline Logger: " + message);
}
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
public class TestAttribute : HandlerAttribute
{
public override ICallHandler CreateHandler(IUnityContainer container)
{
return new TestHandler();
}
}
public class TestHandler : ICallHandler
{
public int Order { get; set; }
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
Console.WriteLine("It's been intercepted.");
if (!Connectivity.IsConnected() && input.Target is IOnline)
{
Console.WriteLine("It's been canceled.");
var offline = ((input.Target as IOnline).Offline);
if (offline == null)
throw new Exception("Online class did not initialized Offline Dispatcher.");
var offlineResult = input.MethodBase.Invoke(offline, this.GetObjects(input.Inputs));
return input.CreateMethodReturn(offlineResult, this.GetObjects(input.Inputs));
}
return getNext()(input, getNext);
}
private object[] GetObjects(IParameterCollection parameterCollection)
{
var parameters = new object[parameterCollection.Count];
int i = 0;
foreach (var parameter in parameterCollection)
{
parameters[i] = parameter;
i++;
}
return parameters;
}
}
}
Related
I want to have a "connectionList" where every element a diffrent connection type (TCP,R232) is stored. I made an abstract interface to "Connect" and "Disconnect" but because "Config" needs diffrent parameters in each class, its not possible to implement it into the interface (or is it?).
I made a little visualitation:
One of my solutions would be to cast before the call like ((TCP)connectionList[0]).Config() but that means that i have to try every possible class type (TCP, RS232, ...) if i want for example to config the "Connection 3".
Im sure there are better solutions then this one.
Code example:
List<IConnection> connectionList = new List<IConnection>();
connectionList.Add(new TCP());
connectionList.Add(new RS232());
connectionList[0].Connect();
connectionList[1].Connect(); // Works
connectionList[0].Config(); //Does not work because not in Interface
public abstract class IConnection
{
public abstract void Connect();
}
public class RS232 : IConnection
{
private int _baudRate;
public void Config(int baudRate) //Diffrent parameters then in TCP
{
_baudRate = baudRate;
}
public override void Connect()
{
Console.WriteLine("RS232 connect()");
}
}
public class TCP : IConnection
{
private int _ipAdress;
public void Config(int ipAdress) //Diffrent parameters then in RS232
{
_ipAdress = ipAdress;
}
public override void Connect()
{
Console.WriteLine("TCP Connect()");
}
}
I think you need some interfaces and maybe an abstract base-connection:
public interface ICanConnect
{
public void Connect();
public void Disconnect();
}
public interface IHaveConfig
{
public IConnectionConfig GetConfig();
}
public interface IConnectionConfig
{
}
public interface ITcpConfiguration: IConnectionConfig
{
public string IpAdress { get; set; }
}
// TODO add class
public interface IRS232Configuration : IConnectionConfig
{
public int BaudRate { get; set; }
}
// TODO add class
The configuration should be done before you use the connections:
public abstract class BaseConnection : ICanConnect, IHaveConfig
{
private IConnectionConfig _config;
public BaseConnection(IConnectionConfig config)
{
_config = config;
}
public IConnectionConfig GetConfig() => _config;
public abstract void Connect();
public abstract void Disconnect();
}
So you can add implementations for the TCP- and RS232-connections:
public class TcpConnection: BaseConnection
{
public TcpConnection(ITcpConfiguration tcpConfig) : base(tcpConfig) { }
public override void Connect()
{
ITcpConfiguration config = (ITcpConfiguration) base.GetConfig();
// here you have the IP-Adress
}
public override void Disconnect()
{
throw new NotImplementedException();
}
}
public class IRS232Connection : BaseConnection
{
public IRS232Connection(IRS232Configuration rs232Config) : base(rs232Config) { }
public override void Connect()
{
IRS232Configuration config = (IRS232Configuration) base.GetConfig();
// here you have the Baud-Rate
}
public override void Disconnect()
{
throw new NotImplementedException();
}
}
With a List<BaseConnection> you can use the connect/disconnect methods(polymorphism):
// initialization
List<BaseConnection> connections = new List<BaseConnection>()
{
new TcpConnection(new TcpConfiguration("192.158.1.38")),
new RS232Connection(new RS232Configuration(2400)),
};
// use them
foreach(BaseConnection con in connections)
{
con.Connect();
con.Disconnect();
}
If you want to change the configuration later, for example the IP-Adress of the TcpConnections:
foreach (TcpConnection con in connections.OfType<TcpConnection>())
{
ITcpConfiguration config = con.GetConfig() as ITcpConfiguration;
config.IpAdress = "192.158.1.39";
}
If you are using Dependency Injection you could configure each connection in your startup, or from a factory, if the configuration is dynamic.
interface IConnection
{
void Connect();
void Disconnect();
}
class RS232Conifguration
{
public int BaudRate { get; set; }
}
class RS232 : IConnection
{
private readonly RS232Conifguration _conifguration;
public RS232(RS232Conifguration conifguration)
{
_conifguration = conifguration;
}
public void Connect()
{
throw new System.NotImplementedException();
}
public void Disconnect()
{
throw new System.NotImplementedException();
}
}
public class TCPConfiguration
{
public int IpAddress { get; set; }
}
public class TCP :IConnection
{
private readonly TCPConfiguration _configuration;
public TCP(TCPConfiguration configuration)
{
_configuration = configuration;
}
public void Connect()
{
throw new System.NotImplementedException();
}
public void Disconnect()
{
throw new System.NotImplementedException();
}
}
var services = new ServiceCollection();
services.AddSingleton(new TCPConfiguration() { IpAddress = 423 });
services.AddScoped<IConnection, TCP>();
services.AddSingleton(new RS232Conifguration() { BaudRate = 4 });
services.AddScoped<IConnection, RS232>();
var provider = services.BuildServiceProvider();
var connections = provider.GetServices<IConnection>();
I have this object structure
public interface IHandler<in TMessage>
{
void HandleMessage(TMessage messageType);
}
public class MessageType1
{
}
public class MessageType2
{
}
public class HandlerMessageType1 : IHandler<MessageType1>
{
public void HandleMessage(T messageType)
{
}
}
public class HandlerMessageType2 : IHandler<MessageType2>
{
public void HandleMessage(T messageType)
{
}
}
and the registration
container.Collection.Register(typeof(IHandler<>), new[]
{
typeof(HandlerMessageType1),
typeof(HandlerMessageType2)
});
how the constructor of the class where this is injected should look like?
public class ClientClass
{
public ClientClass(IEnumerable<IHandler<>> handlers)
{
}
}
like this doesn't work... how the signature of the client class constructor should look like?
this was edited to improve the example.
tkx in advance
Paulo Aboim Pinto
I Know if I understood, but with unity you can have:
public class Handler1 : IHandler
{
public void HandlerType()
{
Console.WriteLine("Handler1");
}
}
public class Handler2 : IHandler
{
public void HandlerType()
{
Console.WriteLine("Handler2");
}
}
public interface IHandler
{
void HandlerType();
}
Unity configuration
public static class DependencyConfiguration
{
public static UnityContainer Config()
{
var unity = new UnityContainer();
unity.RegisterType<IHandler, Handler1>("Handler1");
unity.RegisterType<IHandler, Handler2>("Handler2");
unity.RegisterType<IEnumerable<IHandler>>();
return unity;
}
}
A class to resolve:
public class ListOfTypes
{
private List<IHandler> handlers;
public ListOfTypes(IEnumerable<IHandler> handlers)
{
this.handlers = handlers.ToList();
}
public void PrintHandlers()
{
handlers.ForEach(_ => _.HandlerType());
}
}
The program:
static void Main(string[] args)
{
Console.WriteLine("Resolve sample");
var unity = DependencyConfiguration.Config();
var lstOfTypes = unity.Resolve<ListOfTypes>();
lstOfTypes.PrintHandlers();
Console.ReadLine();
}
Result:
For background I mainly program in Java and am trying to work with/learn generics in a C# project and got stuck.
Here is my problem. From the main method you can see I am trying to set the soldiers task, but I'm getting the error,
cannot convert from 'TaskHeal' to 'TaskBase<SoldierBase>'
It seems that this cast should work as TaskHeal is a child of TaskBase, but it doesn't. Here is my complete code:
public class Main {
static void main(string[] args) {
SoldierMedic myMedic = new SoldierMedic();
myMedic.setTask(new TaskHeal(myMedic)); // Problem!
}
}
public class SoldierBase {
private TaskBase<SoldierBase> currentTask;
public int status;
public void setTask(TaskBase<SoldierBase> newTask) {
this.currentTask = newTask;
}
}
public class SoldierMedic : SoldierBase {
public int healRate = 45;
}
public abstract class TaskBase<T> where T : SoldierBase {
protected T soldier;
public TaskBase(T unit) {
this.soldier = unit;
this.soldier.status = 1;
}
public abstract void preformTask();
}
public class TaskHeal : TaskBase<SoldierMedic> {
public TaskHeal(SoldierMedic unit) : base(unit) { }
public override void preformTask() {
this.soldier.healRate++;
}
}
If you don't mind having an additional base, non-generic class for TaskBase and SoldierBase, you could do this:
class Program
{
static void Main(string[] args)
{
var myMedic = new SoldierMedic();
myMedic.setTask(new TaskHeal(myMedic)); // Problem!
}
}
public class SoldierBase
{
public int status;
}
public class SoldierBase<T> : SoldierBase where T : SoldierBase
{
private TaskBase currentTask;
public void setTask(TaskBase newTask)
{
this.currentTask = newTask;
}
}
public class SoldierMedic : SoldierBase<SoldierMedic>
{
public int healRate = 45;
}
public abstract class TaskBase
{
}
public abstract class TaskBase<T> : TaskBase where T : SoldierBase<T>
{
protected T soldier;
public TaskBase(T unit)
{
this.soldier = unit;
this.soldier.status = 1;
}
public abstract void preformTask();
}
public class TaskHeal : TaskBase<SoldierMedic>
{
public TaskHeal(SoldierMedic unit) : base(unit) { }
public override void preformTask()
{
this.soldier.healRate++;
}
}
If you want this to look more like c# (using properties, proper access modifiers and casing), you'd do something like this:
class Program
{
static void Main(string[] args)
{
var myMedic = new SoldierMedic();
myMedic.CurrentTask = new TaskHeal(myMedic); // Problem!
}
}
public class SoldierBase
{
public int Status { get; set; }
}
public class SoldierBase<T> : SoldierBase where T : SoldierBase
{
public TaskBase CurrentTask { get; set; }
}
public class SoldierMedic : SoldierBase<SoldierMedic>
{
public int HealRate { get; set; } = 45;
}
public abstract class TaskBase
{
}
public abstract class TaskBase<T> : TaskBase where T : SoldierBase<T>
{
protected T Soldier;
public TaskBase(T unit)
{
Soldier = unit;
Soldier.Status = 1;
}
public abstract void PerformTask();
}
public class TaskHeal : TaskBase<SoldierMedic>
{
public TaskHeal(SoldierMedic unit) : base(unit) { }
public override void PerformTask()
{
Soldier.HealRate++;
}
}
I am struggling with how to return Yield for type that is interface type of IBasic. Currently i can have three diffrent types of IBasic: InputData1, InputData2, InputData3.
The problem is on this part of code:
internal class CsvRepo<T> : ICsvRepo<T> where T : IBasic
{
private readonly ICsvSettings _settings;
public CsvRepo(ICsvSettings settings)
{
_settings = settings;
}
public IEnumerable<T> GetRecords()
{
//return from line in File.ReadLines(_settings.Path)
// select line.Split(',') into parts
// where parts.Length == 3
// select new InputData { X = Convert.ToInt32(parts[1]), Y = Convert.ToInt32(parts[2]) };
}
}
in the line: select new InputData
i am going to say something like return new IBasic but diffrent InputDataX has diffrent parameters and i am not sure how to do so? Is it possible?
This is full code:
namespace ClassLibrary3
{
public interface IRepo { }
public interface IRepository<T> : IRepo where T : IBasic { }
public interface ICsvRepo<T> : IRepository<T> where T : IBasic
{
IEnumerable<T> GetRecords();
}
public interface ISqlRepo
{
}
public interface IOracleRepo<T> : IRepository<T> where T : IBasic { }
public interface IRepoX : IRepo { }
public interface ICsvSettings
{
string Path { get; }
string FileName { get; }
}
public interface ISqlSettings
{
string ConnectionString { get; }
string Username { get; }
string Password { get; }
}
internal class CsvSettings : ICsvSettings
{
public string Path { get; set; }
public string FileName { get; set; }
}
internal class SqlSettings : ISqlSettings
{
public string ConnectionString { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
internal class CsvRepo<T> : ICsvRepo<T> where T : IBasic
{
private readonly ICsvSettings _settings;
public CsvRepo(ICsvSettings settings)
{
_settings = settings;
}
public IEnumerable<T> GetRecords()
{
return null;
//return from line in File.ReadLines(_settings.Path)
// select line.Split(',') into parts
// where parts.Length == 3
// select new InputData { X = Convert.ToInt32(parts[1]), Y = Convert.ToInt32(parts[2]) };
}
}
internal class SqlRepo : ISqlRepo
{
private readonly ISqlSettings _settings;
private readonly IRepoX _repoX;
public SqlRepo(ISqlSettings settings, IRepoX repoX)
{
_settings = settings;
_repoX = repoX;
}
}
internal class OracleRepo<T> : IOracleRepo<T> where T : IBasic
{
private readonly ISqlSettings _settings;
private readonly IRepoX _repoX;
public OracleRepo(ISqlSettings settings, IRepoX repoX)
{
_settings = settings;
_repoX = repoX;
}
}
internal class OracleRepo333<T> : IOracleRepo<T> where T : IBasic
{
private readonly ISqlSettings _settings;
private readonly IRepoX _repoX;
public int id;
public OracleRepo333(ISqlSettings settings, IRepoX repoX)
{
_settings = settings;
_repoX = repoX;
}
}
internal class RepoX : IRepoX { }
public class RepoModule : NinjectModule
{
private readonly string _username;
private readonly string _password;
public RepoModule(string username, string password)
{
_username = username;
_password = password;
}
public override void Load()
{
Bind<ICsvSettings>().ToConstant(new CsvSettings
{
FileName = "foo",
Path = Config.Instance.ServerName,
}).InSingletonScope();
Bind<ISqlSettings>().ToConstant(new SqlSettings
{
ConnectionString = "foo",
Password = _password,
Username = _username
}).InSingletonScope();
Bind<IRepoX>().To<RepoX>();
Bind(typeof(ICsvRepo<>)).To(typeof(CsvRepo<>));
Bind(typeof(ISqlRepo)).To(typeof(SqlRepo));
Bind(typeof(IOracleRepo<>)).To(typeof(OracleRepo<>));
Bind(typeof(IOracleRepo<>)).To(typeof(OracleRepo333<>));
}
}
public interface IBasic
{
}
public class InputData1 : IBasic
{
public int X;
public int Y;
}
public class InputData2 : IBasic
{
public string Name;
}
public class InputData3 : IBasic
{
public IEnumerable<string> WhateverList;
}
}
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel(new RepoModule("foo", "bar")); /*some other modules here maybe?*/
//thousand of code lines later...
var csvRepo = kernel.Get<ICsvRepo<InputData1>>();
//var data = FetchData(csvRepo);
var sqlRepo = kernel.Get<ISqlRepo>();
//data = FetchData(sqlRepo);
// var oracleRepo = kernel.Get<IOracleRepo<InputData>>();
//data = FetchData(oracleRepo);
var oracleRepos = kernel.GetAll<List<IOracleRepo<InputData1>>>();}
}
}
//static T[] FetchData<T>(IRepository<InputData> repo)
//{
// throw new NotImplementedException();
//}
}
The problem is that you are trying to return a concrete type where a generic type is expected. Consider the following instantiation of CsvRepo<T>
var repo = new CsvRepo<InputData1Derived>(null);
repo.GetRecords().First().PropFromInputData1Derived
You are instantiating InputData while the caller expects InputDataDerived. This is why the compiler does not let you do this.
You could have several solutions, let CsvRepo could be abstract and implement it for specific classes:
internal abstract class CsvRepo<T> : ICsvRepo<T> where T : IBasic
{
public CsvRepo()
{
}
public abstract IEnumerable<T> GetRecords();
}
internal class InputDataCsvRepo : CsvRepo<InputData1>
{
public override IEnumerable<InputData1> GetRecords()
{
return from line in File.ReadLines(_settings.Path)
select line.Split(',') into parts
where parts.Length == 3
select new InputData { X = Convert.ToInt32(parts[1]), Y = Convert.ToInt32(parts[2]) };
}
}
Or you can make the T parameter have a default constructor and use that (but only properties in IBasic will be initializable which is not what you want probably.
This seems to be one of those situations where if you have a hammer everything looks like a nail. In short there is no need to make the argument for the method generic as already know the concrete return type for any implementation and you want that return type to implement a specific interface.
Remove the generic type constraint T on method GetRecords and replace it with IBasic interface constraint.
public interface ICsvRepo<T> : IRepository<T> where T : IBasic
{
IEnumerable<IBasic> GetRecords();
}
internal class CsvRepo<T> : ICsvRepo<T> where T : IBasic
{
private readonly ICsvSettings _settings;
public CsvRepo(ICsvSettings settings)
{
_settings = settings;
}
public IEnumerable<IBasic> GetRecords()
{
return from line in File.ReadLines(_settings.Path)
select line.Split(',') into parts
where parts.Length == 3
select new InputData { X = Convert.ToInt32(parts[1]), Y = Convert.ToInt32(parts[2]) };
}
}
I altered your code below to only include what is needed to show the solution.
On an unrelated note making fields public is almost always a bad idea because you expose the internals of the class. Make them into properties instead with public getter/setters.
Example:
public class InputData : IBasic
{
public int X {get;set;}
public int Y {get;set;}
}
I want to create builder for my purpose, with such call chain:
User user = new CommonBuilder(new UserNode()).Root //generic parameter, currently is User
.Group.Group.Folder.Build();
Here is the code, which I use:
public abstract class AbstractNode
{
public Guid Id { get; } = Guid.NewGuid();
}
public abstract class AbstractNode<T> where T : AbstractNode<T>
{
}
public class CommonBuilder<T> where T : AbstractNode<T>
{
public T Root { get; private set; }
public CommonBuilder(T root)
{
Root = root;
}
}
public class UserNode : AbstractNode<UserNode>
{
private GroupNode _group;
public GroupNode Group
{
get
{
if (_group is null)
{
_group = new GroupNode();
}
return _group;
}
}
}
public class GroupNode : AbstractNode<GroupNode>
{
private GroupNode _group;
public GroupNode Group
{
get
{
if (_group is null)
{
_group = new GroupNode();
}
return _group;
}
}
private FolderNode _folder;
public FolderNode Folder
{
get
{
if (_folder is null)
{
_folder = new FolderNode();
}
return _folder;
}
}
}
public class FolderNode : AbstractNode<FolderNode>
{
}
The problem is in the Build() method, which need to return Root from CommonBuilder, not the File.
Where must I place Build() method, which must be always called at the end of a chain, which returns Root of a builder?
In case when it's required to make a chain the same object should be returned, even as another interface check first and second examples of implementation Builder with Fluent intefaces
I've tried to implement your case to fit the role, check if it will fits your requirements:
public interface IGroup<T>
{
IGroup<T> Group { get; }
IFolder<T> Folder { get; }
}
public interface IFolder<T>
{
T Build();
}
Builder implements all required interfaces. And returns itself in each call. In general you can put Build method in the builder itself and call it separately after the end of chain execution.
public class CommonBuilder<T> : IGroup<T>, IFolder<T> where T: INode, new()
{
private T _root = new T();
public T Build()
{
return _root;
}
public IGroup<T> Group
{
get
{
_root.MoveToGroup();
return this;
}
}
public IFolder<T> Folder
{
get
{
_root.MoveToFolder();
return this;
}
}
}
Because of generics it's required to set some limitations on generic parameter which is done with INode interface
public interface INode
{
void MoveToGroup();
void MoveToFolder();
}
Testing user object
public class User : INode
{
public StringBuilder Path { get; } = new StringBuilder();
public void MoveToFolder()
{
Path.AppendLine("Folder");
}
public void MoveToGroup()
{
Path.AppendLine("Group");
}
public override string ToString()
{
return Path.ToString();
}
}
And the call will looks like
var user = new CommonBuilder<User>().Group.Group.Folder.Build();
EDIT
Maybe as a the first stage it makes sence to get rid of Fluent interfaces and implement logic using just a Builder:
public class FolderNode : INode<Folder>
{
private readonly Folder _folder = new Folder();
public Folder Build()
{
return _folder;
}
public void AppendGroup()
{
_folder.Path.AppendLine("Folder Group");
}
public void AppendFolder()
{
_folder.Path.AppendLine("Folder Folder");
}
}
public class UserNode : INode<User>
{
private readonly User _user = new User();
public User Build()
{
return _user;
}
public void AppendGroup()
{
_user.Path.AppendLine("Group");
}
public void AppendFolder()
{
_user.Path.AppendLine("Folder");
}
}
public class CommonBuilder<T, TNode> where TNode : INode<T>
{
private readonly TNode _root;
public CommonBuilder(TNode root)
{
_root = root;
}
public T Build()
{
return _root.Build();
}
public CommonBuilder<T, TNode> Group {
get
{
_root.AppendGroup();
return this;
}
}
public CommonBuilder<T, TNode> Folder {
get
{
_root.AppendFolder();
return this;
}
}
}
public interface INode<out T>
{
T Build();
void AppendGroup();
void AppendFolder();
}
public class Folder
{
public StringBuilder Path { get; } = new StringBuilder();
public override string ToString()
{
return Path.ToString();
}
}
public class User
{
public StringBuilder Path { get; } = new StringBuilder();
public override string ToString()
{
return Path.ToString();
}
}
Usage:
var user = new CommonBuilder<User, UserNode>(new UserNode()).Group.Group.Folder.Build();
var folder = new CommonBuilder<Folder, FolderNode>(new FolderNode()).Group.Folder.Group.Folder.Build();