在c#中模拟可变参数模板

nak*_*hli 27 c# variadic-templates

是否有一种众所周知的方法来模拟c#中的可变参数模板功能?

例如,我想编写一个带有任意参数集的lambda的方法.这是我想要的伪代码:

void MyMethod<T1,T2,...,TReturn>(Fun<T1,T2, ..., TReturn> f)
{

}
Run Code Online (Sandbox Code Playgroud)

谢谢

And*_*bel 24

C#泛型与C++模板不同.C++模板是扩展的编译时间,可以使用可变参数模板参数递归使用.C++模板扩展实际上是Turing Complete,因此理论上没有限制模板可以做什么.

C#泛型是直接编译的,其中包含将在运行时使用的类型的空"占位符".

要接受一个带有任意数量参数的lambda,你必须生成大量的重载(通过代码生成器)或接受一个LambdaExpression.

  • 由于这个原因,泛型甚至不能用来避免虚函数调用。我现在真的很喜欢 C++。 (4认同)

Mar*_*ell 8

泛型类型参数(无论是方法还是类型)​​都没有varadic支持.您将不得不添加大量重载.

varadic支持仅适用于数组,通过params,即

void Foo(string key, params int[] values) {...}
Run Code Online (Sandbox Code Playgroud)

令人沮丧的是 - 你甚至会如何引用那些不同T*的方法来编写通用方法?也许你最好的选择是采取Type[]或类似(取决于具体情况).

  • C++ 0x给出了关于如何引用它们的答案.实际上,它与Haskell并没有太大的不同.你选择一个头,并递归传递尾部,例如`Foo <T ...>`< - 主要模板,`Foo <Head,Tail ......>`< - 一个实际的实现.C++模板与haskell和模式匹配有很多共同之处. (6认同)

Rob*_*ack 6

我知道这是一个古老的问题,但是如果您想要做的只是打印这些类型的简单方法,那么您可以非常轻松地使用"Tuple"或"动态"的任何额外内容:

private static void PrintTypes(params dynamic[] args)
{
    foreach (var arg in args)
    {
        Console.WriteLine(arg.GetType());
    }
}

static void Main(string[] args)
{
    PrintTypes(1,1.0,"hello");
    Console.ReadKey();
}
Run Code Online (Sandbox Code Playgroud)

将打印"System.Int32","System.Double","System.String"

如果你想对这些事情采取一些行动,据我所知你有两个选择.一种方法是信任程序员这些类型可以执行兼容操作,例如,如果您想创建一个方法来对任意数量的参数求和.您可以编写如下方法,说明您希望如何接收结果,并且我认为唯一的先决条件是+操作在这些类型之间起作用:

    private static void AddToFirst<T>(ref T first, params dynamic[] args)
    {
        foreach (var arg in args)
        {
            first += arg;
        }
    }

    static void Main(string[] args)
    {
        int x = 0;
        AddToFirst(ref x,1,1.5,2.0,3.5,2);
        Console.WriteLine(x);

        double y = 0;
        AddToFirst(ref y, 1, 1.5, 2.0, 3.5, 2);
        Console.WriteLine(y);

        Console.ReadKey();
    }
Run Code Online (Sandbox Code Playgroud)

有了这个,第一行的输出将是"9",因为添加到int,第二行将是"10",因为.5s没有得到舍入,添加为double.此代码的问题是如果您在列表中传递一些不兼容的类型,它将会出现错误,因为类型无法一起添加,并且您不会在编译时看到该错误,仅在运行时.

因此,根据您的使用情况,可能还有另一种选择,这就是为什么我说首先有两个选择.假设您知道可能类型的选择,您可以创建一个接口或抽象类,并使所有这些类型实现接口.例如,以下内容.对不起,这有点疯狂.它可能是简单的.

    public interface Applyable<T>
    {
        void Apply(T input);

        T GetValue();
    }

    public abstract class Convertable<T>
    {
        public dynamic value { get; set; }

        public Convertable(dynamic value)
        {
            this.value = value;
        }

        public abstract T GetConvertedValue();
    }        

    public class IntableInt : Convertable<int>, Applyable<int>
    {
        public IntableInt(int value) : base(value) {}

        public override int GetConvertedValue()
        {
            return value;
        }

        public void Apply(int input)
        {
            value += input;
        }

        public int GetValue()
        {
            return value;
        }
    }

    public class IntableDouble : Convertable<int>
    {
        public IntableDouble(double value) : base(value) {}

        public override int GetConvertedValue()
        {
            return (int) value;
        }
    }

    public class IntableString : Convertable<int>
    {
        public IntableString(string value) : base(value) {}

        public override int GetConvertedValue()
        {
            // If it can't be parsed return zero
            int result;
            return int.TryParse(value, out result) ? result : 0;
        }
    }

    private static void ApplyToFirst<TResult>(ref Applyable<TResult> first, params Convertable<TResult>[] args)
    {
        foreach (var arg in args)
        {                
            first.Apply(arg.GetConvertedValue());  
        }
    }

    static void Main(string[] args)
    {
        Applyable<int> result = new IntableInt(0);
        IntableInt myInt = new IntableInt(1);
        IntableDouble myDouble1 = new IntableDouble(1.5);
        IntableDouble myDouble2 = new IntableDouble(2.0);
        IntableDouble myDouble3 = new IntableDouble(3.5);
        IntableString myString = new IntableString("2");

        ApplyToFirst(ref result, myInt, myDouble1, myDouble2, myDouble3, myString);

        Console.WriteLine(result.GetValue());

        Console.ReadKey();
    }
Run Code Online (Sandbox Code Playgroud)

输出"9"将与原始Int代码相同,除了您可以实际传入的唯一值,因为参数是您实际已定义的内容,并且您知道它们将起作用并且不会导致任何错误.当然,你必须创建新的类,如DoubleableInt,DoubleableString等,以便重新创建10的第二个结果.但这只是一个例子,所以你根本不会尝试添加任何东西根据您正在编写的代码,您将从最适合您的实现开始.

希望有人可以改进我在这里写的内容或使用它来看看如何在C#中完成.


小智 5

除了上面提到的那些之外的另一种选择是使用 Tuple<,> 和反射,例如:

class PrintVariadic<T>
{
    public T Value { get; set; }

    public void Print()
    {
        InnerPrint(Value);
    }

    static void InnerPrint<Tn>(Tn t)
    {
        var type = t.GetType();
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Tuple<,>))
        {
            var i1 = type.GetProperty("Item1").GetValue(t, new object[]{});
            var i2 = type.GetProperty("Item2").GetValue(t, new object[]{ });
            InnerPrint(i1);
            InnerPrint(i2);
            return;
        }
        Console.WriteLine(t.GetType());
    }
}

class Program
{
    static void Main(string[] args)
    {
        var v = new PrintVariadic<Tuple<
            int, Tuple<
            string, Tuple<
            double, 
            long>>>>();
        v.Value = Tuple.Create(
            1, Tuple.Create(
            "s", Tuple.Create(
            4.0, 
            4L)));
        v.Print();
        Console.ReadKey();
    }
}
Run Code Online (Sandbox Code Playgroud)