【问题标题】:Implementing multiparameter C++ template like behaviour on C# using Policy Pattern使用策略模式在 C# 上实现类似行为的多参数 C++ 模板
【发布时间】:2016-03-16 22:33:28
【问题描述】:

我正在尝试使用基于 this answer 的 C# 泛型和策略模式来实现类似 c++ 的模板

这是模式的示例:

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);
    }

}

我的预期行为是这样的:

  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

但到目前为止,我无法让 .net 接受 Generic Extension with less arguments than parameters

我可以显式调用接口,这太冗长了,违背了所有这些的目的。

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

我想用一个包装器来解决这个问题:

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);
    }
}

但是包装器无法解析被包装函数的两种类型,失败:

错误 1 ​​类型 'MyClass' 不能用作类型参数 'P' 在泛型类型或方法中 'MyClassExtension.wrappedDoSomething(P, U)'。没有 从 'MyClass' 到的隐式引用转换 'ISomePolicy'

感谢任何解决参数问题或重新设计所有这些问题的见解。


对于上下文,这将用于包装 I/O 转换器。 T 在我的例子中是目标 I/O 格式,U 是我的框架使用的数据的对象表示。

我知道这可以通过委托或接口轻松实现,但目标是框架用户可以轻松实例化所需的翻译,如果实现不存在,则可以轻松地将其添加到通用接口.


编辑:从另一个泛型方法/类中解析一个泛型方法似乎都不适用于单声道。

【问题讨论】:

  • 为什么需要 T 参数?
  • 在编译时检查ISomePolicy&lt;T, U&gt; 是否由MyClass 实现(参见MyClassExtension

标签: c# generics extension-methods implicit-conversion template-specialization


【解决方案1】:

通常,策略不应包含数据。例如,

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);
}

如果您想结合政策和数据,那么您可以考虑不同的界面

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);
}

或者这两个概念的结合

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);
}

用法:

// 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);

【讨论】:

  • 谢谢,我遇到了一些问题。在这种情况下,运算符=&gt; 是什么意思。它没有在 4.5 框架上编译。我用包装 { } 替换了它,但这些策略没有按预期工作。第一个不是选项,第二个不会阻止var my = new MyClass&lt;double&gt;(0.5); my.doSomething(1),第三个不允许我构造var x = new MySuperClass&lt;SomePolicyImplementation, double&gt;(0.5);,除非实施所有可能的策略。
  • @xvan =&gt; 在这里用于表达式主体的函数成员,正如您所期望的那样,只有一个方便的版本用于编写单个表达式方法:github.com/dotnet/roslyn/wiki/…
【解决方案2】:

尝试了您的代码,但即使是简单的调用也无法开箱即用。主要问题是 MyClass 包含未知元素类型“myEement”——该类型不能从函数调用参数中推断出来。但是 - 如果您进行概括并省略对象类型 - 您的示例将以开箱即用的方式工作:

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");
    }
}

什么取决于 myEement(或者您可能是指 myElement) - 如果需要,您可以在运行时获取它的类型。

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

但是 - 反射总是可能会减慢您的执行速度。如果您不打算创建具有大量实例的超重类层次结构 - 那么这应该足以满足您的需求。

【讨论】:

  • 谢谢,但doSomething() 的实现取决于myElement 类型。这种模式的想法是在编译时捕获尝试对doSomething() 的未实现调用。如果采用反射方式,则使用反射表可以实现更简洁的实现。顺便说一句,很好地抓住了错字。
  • 我的解决方案确实发现了不正确的类型使用 - 试试吧。但是如果你想拥有强类型的数据类型——它只能在运行时实现。您可以创建类似 ISomePolicy 的类(将接口转换为类,并将 T myElement 移动到该类中),它具有强大的数据类型,但是您会使实现更加复杂。这是任何语言的典型问题 - 使一件事变得简单会在另一端带来更多的复杂性。
  • 如果我接受丢失编译时警告,我可以重载doSomething(typea a, typeb b),并在doSomething(object a, object b) 上抛出异常;无论如何,我会尝试你的解决方案。
  • 如果你不关心代码的性能,我可以推荐这样的东西:stackoverflow.com/a/36119881/2338477 - 这个类可以调用任意数量的参数,但只使用 ref 参数,而不是 out。这是因为 c# 编程语言的限制。
猜你喜欢
  • 1970-01-01
  • 2014-01-24
  • 2018-05-27
  • 2013-02-23
  • 2015-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-03
相关资源
最近更新 更多