4

I've hurt my brain here.

Some background:

I have a multiple services in multitier app. Some of them are short-living (and should be consumed with using) and some of them are not (so I just inject them in any class I want and use directly). My client code has different data interfaces and random using keywords.

For some reasons (for SCIENCE mostly) I decided to refactor it a bit. I've made an universal DataServiceAdapter<> class which should be closed for every single service I use and this universal class encapsulate using logic from client.

DataServiceAdapter looks like this (short working example here on dotnetfiddle)

public class DataServiceAdapter<TService> : IDataService
    where TService : IDataService, new()
{
    private readonly TService _service;
    private readonly Func<Func<TService, dynamic>, dynamic> _run;

    public DataServiceAdapter()
    {
        if (typeof(IDisposable).IsAssignableFrom(typeof(TService)))
        {
            _run = RunWithUsing;
        }
        else
        {
            _service = new TService();
            _run = RunWithoutUsing;
        }
    }

    public bool Foo()
    {
        return _run(x => x.Foo());
    }

    public int Bar(string arg)
    {
        return _run(x => x.Bar(arg));
    }

    private dynamic RunWithUsing(Func<TService, dynamic> func)
    {
        using (var service = (IDisposable) new TService())
        {
            return func((TService)service);
        }
    }

    private dynamic RunWithoutUsing(Func<TService, dynamic> func)
    {
        return func(_service);
    }
}

public interface IDataService
{
    bool Foo();
    int Bar(string arg);
}

client supposed to work with it like this:

//class variable. Get this from IoC container
private readonly IDataService _service;
//somewhere in methods
var resultData = _service.Foo(); //full static type checking

But because my real life version of IDataService contains dozens of method I rewrote DataServiceAdapter like this (only changes, dotnetfiddle link):

public class DataServiceAdapter<TService> : IDataServiceAdapter<TService>
    where TService : IDataService, new()
{
    public dynamic Run(Func<TService, dynamic> func)
    {
        return _serviceUsing(func);
    }
}

public interface IDataServiceAdapter<out TService>
    where TService : IDataService
{
    dynamic Run(Func<TService, dynamic> func);
}

client now works with that version of DataServiceAdapter this way:

//class variable. Get this from IoC container
private readonly IDataServiceAdapter<IDataService> _service;
//somewhere in methods
var resultData = _service.Run(s => s.Foo()); 

It works without static checking because of dynamic

So (we are close to question) I decided to rewrote it again (for SCIENCE) and return static safety, but without necessity of wrapping all IDataService methods in my adapter class.

First of all I wrote this delegate:

private delegate TRes RunDelegate<TResult>(Func<TService, TResult> func);

But I just cant create variable of such delegate and pass it some method I wish to use like I did above.

Question: Is there any way to implement my design thought there (generic service provider which call whether method with using or method without it and has type-safety)?

Tyree Jackson
  • 2,588
  • 16
  • 22
Szer
  • 3,426
  • 3
  • 16
  • 36

2 Answers2

3

You could delegate out the run logic to a runner class that implements an IRunner interface. You could optionally inject this if you felt the need to.

Like this:

using System;

public class Program
{
    public static void Main()
    {
        //Dependency register logic here. Choose either

        //var service = new DataServiceAdapter<SpecificNotDisposableDataService>();
        var service = new DataServiceAdapter<SpecificDisposableDataService>();

        var client = new Client(service);
        client.ClientMethod();
        Console.ReadLine();
    }
}

public class Client
{
    private readonly IDataServiceAdapter<IDataService> _service;
    public Client(IDataServiceAdapter<IDataService> service)
    {
        _service = service;
    }

    public void ClientMethod()
    {
        Console.WriteLine(_service.Run(s => s.Foo()));
        Console.WriteLine(_service.Run(s => s.Bar("Hello")));
    }
}

public class DataServiceAdapter<TService> : IDataServiceAdapter<TService>
    where TService : IDataService, new()
{
    private interface IRunner
    {
        T Run<T>(Func<TService, T> func);
    }
    private class WithUsing : IRunner
    {
        public T Run<T>(Func<TService, T> func)
        {
            using (var service = (IDisposable) new TService())
            {
                return func((TService)service);
            }
        }
    }
    private class WithoutUsing : IRunner
    {
        private readonly TService _service = new TService();
        public T Run<T>(Func<TService, T> func)
        {
            return func(_service);
        }
    }
    private readonly IRunner _runner;

    public DataServiceAdapter()
    {
        if (typeof(IDisposable).IsAssignableFrom(typeof(TService)))
        {
            _runner = new WithUsing();
        }
        else
        {
            _runner = new WithoutUsing();
        }
    }

    public T Run<T>(Func<TService, T> func)
    {
        return _runner.Run<T>(func);
    }
}

public class SpecificDisposableDataService : IDataService, IDisposable
{
    public bool Foo()
    {
        return true;
    }

    public int Bar(string arg)
    {
        return arg.Length;
    }

    public void Dispose()
    {
        //Dispose logic here
    }
}

public class SpecificNotDisposableDataService : IDataService
{
    public bool Foo()
    {
        return false;
    }

    public int Bar(string arg)
    {
        return arg.Length*2;
    }
}

public interface IDataServiceAdapter<out TService>
    where TService : IDataService
{
    T Run<T>(Func<TService, T> func);
}

public interface IDataService
{
    bool Foo();
    int Bar(string arg);
}

Here is a working dotnetfiddle of the above: https://dotnetfiddle.net/FmNpju

And here is another running both services: https://dotnetfiddle.net/KxEGRB

Tyree Jackson
  • 2,588
  • 16
  • 22
  • 1
    Wow, that's a twist. It looks ten times better than my implementation and it works. Thanks! – Szer Aug 11 '15 at 14:41
  • 1
    @Szer You are welcome! Note the double use of `DataServiceAdapter` as a both a class and as a parametric/generic namespace that shares the `TService` type parameter with the nested `IRunner` interface and the classes that implement the interface. This technique helps to keep code DRY when types are clearly properly coupled [via interfaces where appropriate of course and where one supports the other exclusively]. Check out http://stackoverflow.com/tags/parametric-namespaces/info for more info. – Tyree Jackson Aug 11 '15 at 15:30
1

This could be handled by a simple extension method:

public static TResult Run<TService, TResult>(this IDataService<TService> @this, 
                                             Func<TService, TResult> func)
{
  if (typeof(IDisposable).IsAssignableFrom(typeof(TService)))
  {
    using (var service = (@this.GetInstance() as IDisposable))
    {
      return func(@this);
    }
  }
  else
  {
    return func(@this.GetInstance());
  }
}

Just implement the GetInstance method to either return a new service (when IDisposable) or the "singleton" (when not IDisposable).

You'll find that when using functional paradigms (like lambda's / delegates in this case), it pays to go all the way, rather than trying to force some functional/inheritance-oriented hybrid.

And of course, if you've dipped your toes in functional programming before, even this piece of code is already nagging you - it's suspiciously generalizable; why limit yourself to this kind of operation?

You can use the same approach to move the logic into your service provider. The point is that you keep the execute method generic, which allows you to retain strong typing. Just let the method handle the execution instead of trying to store a delegate somewhere.

If you really want to delegate this further, you could use an extra interface:

interface IExecutor<TService>
{
  TResult Execute<TResult>(Func<TService, TResult> func);
}

Note that the interface is only generic on TService - it's the Executemethod that adds the TResult for type safety again. This means you can easily store either implementation in the same field - you only pass the TResult when actually asking for the Execute operation, and by then you know what TResult is supposed to be.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • Yeah, this will solve my problem, but this particular check will be done every time I call method from my service class, so my implementation checks only once (in constructor). I'm not sure how "heavy" this check is so may be your solution is good enough. I'll try – Szer Aug 11 '15 at 14:29
  • @Szer Forget about things like that. You're already handling communication with a service - and I imagine it's a remote service, right? Checks like this are on a completely different scale than the simplest remote request-response. This is exactly the kind of code where thinking about performance is detriminal - if you really had a good reason to optimize this, there are ways around the checks, but it simply isn't worth it unless this is the performance bottleneck - and it isn't going to be. – Luaan Aug 11 '15 at 14:33
  • yes, you are right. This check will be hundred times lighter than these service calls... – Szer Aug 11 '15 at 14:37