8

I'm trying to implement a c++ like template with C# generics and policy pattern based on this answer

This is a sample of the pattern:

interface ISomePolicy<T,U>
{
    void _doSomething(U u);
}


class MyClass<T,U>:
     ISomePolicy<int, double>,
     ISomePolicy<int, int>
    {

    internal T myElement {get;set;}

    public MyClass(T Element) {
        myElement = Element;
    }

    void ISomePolicy<int, double>._doSomething(double u)
    {
        Console.WriteLine("this is int, double");
    }

    void ISomePolicy<int, int>._doSomething(int u)
    {
        Console.WriteLine("this is int, int");
    }

    }

static class MyClassExtension
{

    //What I want to do
    public static void doSomething<P, T, U>(this P oTh, U u) where P : MyClass<T, U>, ISomePolicy<T, U>
    {
        oTh._doSomething(u);
    }

}

My intended behaviour is like this:

  MyClass<int, double> oClass = new MyClass<int, double>(3);

  oClass.doSomething(0.5); //This works
  oClass.doSomething(1);   //This works

  oClass.doSomething("This should fail"); //Breaks at compile time           

  MyClass<string, double> oClass1 = new MyClass<string, double>("sadfsd"); //Not implemented, wasn't able to prevent the construction.

  oClass1.doSomething(0.4); //Breaks at compile time

But so far I wasn't able to make .net accept Generic Extension with less arguments than parameters

I can call the interface explicitly, which is horrible verbose defeating the purpose of all this.

oClass.doSomething < MyClass<int, double>,int,double>(0.5);

I thought of working that around with a wrapper:

static class MyClassExtension{
    private static void wrappedDoSomething<P, T, U>(this P oTh, U u) 
    where P : MyClass<T, U>, ISomePolicy<T, U>
    {
        oTh._doSomething(u);
    }

    public static void doSomething<T, U>(this MyClass<T, U> oTh, U u)

    {
        oTh.wrappedDoSomething<MyClass<T, U>, T, U>(u);
    }
}

But the wrapper can't resolve both types for the wrapped function, failing with:

Error 1 The type 'MyClass' cannot be used as type parameter 'P' in the generic type or method 'MyClassExtension.wrappedDoSomething(P, U)'. There is no implicit reference conversion from 'MyClass' to 'ISomePolicy'

Any insights to fix the parameters issue or redesign all this are appreciated.


For context this would be used to wrap I/O translators. T in my case would be the target I/O format, and U the object representation of that data used by my framework.

I'm aware that this can be easily achieved with delegates or interfaces, but the objective is that the framework user easily instantiates the desired translation, and if an implementation doesn't exists, it can be trivially added to a common interface.


EDIT: Resolving a generic method from inside another generic method/class neither seems to work on mono.

Community
  • 1
  • 1
xvan
  • 4,554
  • 1
  • 22
  • 37

2 Answers2

3

Usually, policies should not contain data. For example,

interface ISomePolicy<T, U>
{
    void _doSomething(T t, U u);
}

struct SomePolicyImplementation :
    ISomePolicy<int, double>,
    ISomePolicy<int, int>,
    ISomePolicy<double, double>
{
    void ISomePolicy<int, int>._doSomething(int t, int u)
        => Console.WriteLine("this is int, int");

    void ISomePolicy<int, double>._doSomething(int t, double u)
        => Console.WriteLine("this is int, double");

    void ISomePolicy<double, double>._doSomething(double t, double u)
        => Console.WriteLine("this is double, double");
}

static class SomePolicyExtension
{
    public static void doSomething<P, T, U>(this P policy, T t, U u)
        where P : struct, ISomePolicy<T, U>
        => policy._doSomething(t, u);
}

If you would like to combine policies and data then you may consider different interface

interface IEmbeddedPolicy<U>
{
    void _doSomething(U u);
}

class MyClass<T> :
    IEmbeddedPolicy<double>,
    IEmbeddedPolicy<int>
{
    public T Value { get; }

    public MyClass(T value) { this.Value = value; }

    void IEmbeddedPolicy<int>._doSomething(int u)
        => Console.WriteLine("this is T, int");

    void IEmbeddedPolicy<double>._doSomething(double u)
        => Console.WriteLine("this is T, double");
}

static class EmbeddedPolicyExtension
{
    public static void doSomething<E, U>(this E embedded, U u)
        where E : IEmbeddedPolicy<U>
        => embedded._doSomething(u);
}

Or combination of these two concepts

class MySuperClass<P, T>:
    IEmbeddedPolicy<double>,
    IEmbeddedPolicy<int>
    where P: struct, ISomePolicy<T, double>, ISomePolicy<T, int>
{
    public T Value { get; }

    public MySuperClass(T value) { this.Value = value; }

    void IEmbeddedPolicy<int>._doSomething(int u)
        => new P()._doSomething(this.Value, u);

    void IEmbeddedPolicy<double>._doSomething(double u)
        => new P()._doSomething(this.Value, u);
}

Usage:

// independent policy
var policy = new SomePolicyImplementation();

policy.doSomething(5, 6);
policy.doSomething(5, 6.7);
policy.doSomething(5.3, 6.7);

// embedded policy
var my = new MyClass<int>(54);
my.doSomething(5);
my.doSomething(89.7);

// combination
var x = new MySuperClass<SomePolicyImplementation, int>(53);
x.doSomething(9);
x.doSomething(18.3);
Sergey Shandar
  • 2,357
  • 18
  • 25
  • Thanks, I had some issues with this. what's the meaning of operator `=>` in this context. It didn't compile on 4.5 framework. I replaced it by wrapping `{ }` but the policies don't work as expected. The first one is not an option, the second one doesn't block `var my = new MyClass(0.5); my.doSomething(1)`, The third doesn't let me construct `var x = new MySuperClass(0.5);` unless all possible policies are implemented. – xvan Mar 28 '16 at 18:45
  • 1
    @xvan `=>` is used here for expression-bodied function members and just as you expected only a handy version for writing single expression methods: https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6#expression-bodied-function-members – mbx Apr 06 '16 at 14:58
3

Tried your code, but even simple calls did not work out of box. Main problem is that MyClass contains unknown element type 'myEement' - that type cannot be deduced from function call parameters. However - if you make a generalization and omit object type - your sample will work in out of box manner:

using System;
using System.Collections.Generic;

interface ISomePolicy<U> 
{
    void _doSomething(U u);
}

public class MyClass<U> :
     ISomePolicy<double>,
     ISomePolicy<int>
{
    internal object myEement { get; set; }

    public MyClass(object Element)
    {
        myEement = Element;
    }

    void ISomePolicy<double>._doSomething(double u)
    {
        Console.WriteLine("this is double");
    }

    void ISomePolicy<int>._doSomething(int u)
    {
        Console.WriteLine("this is int");
    }
}

static class MyClassExtension
{
    public static void doSomething<P, U>(this P oTh, U u) where P : ISomePolicy<U>
    {
        oTh._doSomething(u);
    }
}

class Program
{
    static void Main()
    {
        MyClass<double> oClass = new MyClass<double>(3);
        oClass.doSomething(0.5); //This works
        oClass.doSomething(1);   //This works            
        //oClass.doSomething("Will not work");
    }
}

What is up to myEement (or you probably meant myElement) - you can get's it's type at run-time if necessary.

myElement.GetType(), or cast to it - e.g.
if( myElement is int ) DoSomethingWithInt( (int) myElement );

However - reflection always might slow down your execution. If you don't intend to create super heavy class hierarchy with huge amount of instances - then this should be sufficient for your needs.

TarmoPikaro
  • 4,723
  • 2
  • 50
  • 62
  • Thanks, but `doSomething()` implementation depends on `myElement` type. The idea of this pattern is to catch at compile time when a non implemented call to `doSomething()` is attempted. If going the reflection way, there is a cleaner implementation using a reflection table. BTW, good catch on the typo. – xvan Apr 07 '16 at 12:35
  • My solution does catches improper type usage - just try it out. But if you want to have strongly typed data type - it van be achieved only at run-time. You could create ISomePolicy like class (convert interface to class, and move T myElement into that class) which has strong data type, but then you make implementation more complex. It typical problem of any language - making one thing simple creates more complexity on another end. – TarmoPikaro Apr 07 '16 at 15:02
  • If I accept loosing compile time warnings, I could just overload `doSomething(typea a, typeb b)`, and throw and exception on `doSomething(object a, object b)`; Anyway I'll try your solution. – xvan Apr 07 '16 at 15:06
  • If you don't care about performance of your code, I can recommend something like this: http://stackoverflow.com/a/36119881/2338477 - this class can call with any amount of arguments but use only ref parameter, not out. This comes because of c# programming language limitations. – TarmoPikaro Apr 07 '16 at 15:11