35

C# で可変個引数テンプレート機能をシミュレートするためのよく知られた方法はありますか?

たとえば、任意のパラメーター セットを持つラムダを取るメソッドを書きたいと思います。ここに私が欲しいものを疑似コードで示します:

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

}
4

6 に答える 6

27

C# ジェネリックは C++ テンプレートと同じではありません。C++ テンプレートはコンパイル時に拡張され、可変個引数のテンプレート引数で再帰的に使用できます。C++ テンプレート展開は実際にはチューリング完全であるため、テンプレートで実行できることに理論的な制限はありません。

C# ジェネリックは、実行時に使用される型の空の "プレースホルダー" を使用して直接コンパイルされます。

任意の数の引数を取るラムダを受け入れるには、(コード ジェネレーターを使用して) 多くのオーバーロードを生成するか、LambdaExpression.

于 2011-07-27T13:25:48.543 に答える
9

(メソッドまたは型のいずれかで) ジェネリック型引数の varadic サポートはありません。多くのオーバーロードを追加する必要があります。

varadic のサポートは、配列でのみ利用可能ですparams

void Foo(string key, params int[] values) {...}

T*重要なことに、ジェネリックメソッドを作成するために、これらのさまざまなものをどのように参照しますか? おそらく、あなたの最良の選択肢は、または類似のものを取ることですType[](文脈によって異なります)。

于 2011-07-27T13:18:46.957 に答える
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();
    }
}
于 2011-09-03T14:23:57.590 に答える
4

このパターンに名前があるかどうかは必ずしもわかりませんが、戻り値の型がすべての渡された値の型情報を保持して、無制限の量の値を渡すことができる再帰的なジェネリック インターフェイスの次の定式化にたどり着きました。

public interface ITraversalRoot<TRoot>
{
    ITraversalSpecification<TRoot> Specify();
}

public interface ITraverser<TRoot, TCurrent>: ITraversalRoot<TRoot>
{
    IDerivedTraverser<TRoot, TInclude, TCurrent, ITraverser<TRoot, TCurrent>> AndInclude<TInclude>(Expression<Func<TCurrent, TInclude>> path);
}

public interface IDerivedTraverser<TRoot, TDerived, TParent, out TParentTraverser> : ITraverser<TRoot, TParent>
{
    IDerivedTraverser<TRoot, TInclude, TDerived, IDerivedTraverser<TRoot, TDerived, TParent, TParentTraverser>> FromWhichInclude<TInclude>(Expression<Func<TDerived, TInclude>> path);

    TParentTraverser ThenBackToParent();
}

ここでは型システムのキャストや「ごまかし」はありません。より多くの値をスタックし続けることができ、推論された戻り値の型はより多くの情報を格納し続けます。使い方はこんな感じです。

var spec = Traversal
    .StartFrom<VirtualMachine>()             // ITraverser<VirtualMachine, VirtualMachine>
    .AndInclude(vm => vm.EnvironmentBrowser) // IDerivedTraverser<VirtualMachine, EnvironmentBrowser, VirtualMachine, ITraverser<VirtualMachine, VirtualMachine>>
    .AndInclude(vm => vm.Datastore)          // IDerivedTraverser<VirtualMachine, Datastore, VirtualMachine, ITraverser<VirtualMachine, VirtualMachine>>
    .FromWhichInclude(ds => ds.Browser)      // IDerivedTraverser<VirtualMachine, HostDatastoreBrowser, Datastore, IDerivedTraverser<VirtualMachine, Datastore, VirtualMachine, ITraverser<VirtualMachine, VirtualMachine>>>
    .FromWhichInclude(br => br.Mountpoints)  // IDerivedTraverser<VirtualMachine, Mountpoint, HostDatastoreBrowser, IDerivedTraverser<VirtualMachine, HostDatastoreBrowser, Datastore, IDerivedTraverser<VirtualMachine, Datastore, VirtualMachine, ITraverser<VirtualMachine, VirtualMachine>>>>
    .Specify();                              // ITraversalSpecification<VirtualMachine>

ご覧のように、数回の連鎖呼び出しの後、型シグネチャは基本的に判読不能になりますが、型推論が機能し、ユーザーに適切な型を提案する限り、これは問題ありません。

私の例ではFuncs 引数を扱っていますが、おそらくこのコードを適応させて、任意の型の引数を処理することができます。

于 2016-07-11T18:44:28.090 に答える
2

シミュレーションの場合、次のように言えます。

void MyMethod<TSource, TResult>(Func<TSource, TResult> f) where TSource : Tparams {

Tparams可変引数の実装クラスになる場所。ただし、フレームワークはそれを行うためのすぐに使用できるものを提供していません。 ActionFuncTupleなどはすべて、署名の長さが制限されています。私が考えることができる唯一のことは、CRTPを適用することです..誰かがブログを書いているのを見つけられない方法で.. これが私の実装です:


*: 言及してくれた @SLaks に感謝しTuple<T1, ..., T7, TRest>ます。再帰的な方法でも機能します。クラス定義ではなく、コンストラクターとファクトリーメソッドで再帰的であることに気付きました。型の最後の引数の実行時型チェックを行い、 ;TRestである必要があります。ITupleInternalこれは動作が少し異なります。


  • コード

    using System;
    
    namespace VariadicGenerics {
        public interface INode {
            INode Next {
                get;
            }
        }
    
        public interface INode<R>:INode {
            R Value {
                get; set;
            }
        }
    
        public abstract class Tparams {
            public static C<TValue> V<TValue>(TValue x) {
                return new T<TValue>(x);
            }
        }
    
        public class T<P>:C<P> {
            public T(P x) : base(x) {
            }
        }
    
        public abstract class C<R>:Tparams, INode<R> {
            public class T<P>:C<T<P>>, INode<P> {
                public T(C<R> node, P x) {
                    if(node is R) {
                        Next=(R)(node as object);
                    }
                    else {
                        Next=(node as INode<R>).Value;
                    }
    
                    Value=x;
                }
    
                public T() {
                    if(Extensions.TypeIs(typeof(R), typeof(C<>.T<>))) {
                        Next=(R)Activator.CreateInstance(typeof(R));
                    }
                }
    
                public R Next {
                    private set;
                    get;
                }
    
                public P Value {
                    get; set;
                }
    
                INode INode.Next {
                    get {
                        return this.Next as INode;
                    }
                }
            }
    
            public new T<TValue> V<TValue>(TValue x) {
                return new T<TValue>(this, x);
            }
    
            public int GetLength() {
                return m_expandedArguments.Length;
            }
    
            public C(R x) {
                (this as INode<R>).Value=x;
            }
    
            C() {
            }
    
            static C() {
                m_expandedArguments=Extensions.GetExpandedGenericArguments(typeof(R));
            }
    
            // demonstration of non-recursive traversal
            public INode this[int index] {
                get {
                    var count = m_expandedArguments.Length;
    
                    for(INode node = this; null!=node; node=node.Next) {
                        if(--count==index) {
                            return node;
                        }
                    }
    
                    throw new ArgumentOutOfRangeException("index");
                }
            }
    
            R INode<R>.Value {
                get; set;
            }
    
            INode INode.Next {
                get {
                    return null;
                }
            }
    
            static readonly Type[] m_expandedArguments;
        }
    }
    

C<>の宣言で継承されたクラスの型パラメーターに注意してください。

public class T<P>:C<T<P>>, INode<P> {

でありT<P>、クラスT<P>はネストされているため、次のようなクレイジーなことを実行できます。

  • テスト

    [Microsoft.VisualStudio.TestTools.UnitTesting.TestClass]
    public class TestClass {
        void MyMethod<TSource, TResult>(Func<TSource, TResult> f) where TSource : Tparams {
            T<byte>.T<char>.T<uint>.T<long>.
            T<byte>.T<char>.T<long>.T<uint>.
            T<byte>.T<long>.T<char>.T<uint>.
            T<long>.T<byte>.T<char>.T<uint>.
            T<long>.T<byte>.T<uint>.T<char>.
            T<byte>.T<long>.T<uint>.T<char>.
            T<byte>.T<uint>.T<long>.T<char>.
            T<byte>.T<uint>.T<char>.T<long>.
            T<uint>.T<byte>.T<char>.T<long>.
            T<uint>.T<byte>.T<long>.T<char>.
            T<uint>.T<long>.T<byte>.T<char>.
            T<long>.T<uint>.T<byte>.T<char>.
            T<long>.T<uint>.T<char>.T<byte>.
            T<uint>.T<long>.T<char>.T<byte>.
            T<uint>.T<char>.T<long>.T<byte>.
            T<uint>.T<char>.T<byte>.T<long>.
            T<char>.T<uint>.T<byte>.T<long>.
            T<char>.T<uint>.T<long>.T<byte>.
            T<char>.T<long>.T<uint>.T<byte>.
            T<long>.T<char>.T<uint>.T<byte>.
            T<long>.T<char>.T<byte>.T<uint>.
            T<char>.T<long>.T<byte>.T<uint>.
            T<char>.T<byte>.T<long>.T<uint>.
            T<char>.T<byte>.T<uint>.T<long>
            crazy = Tparams
                // trying to change any value to not match the 
                // declaring type makes the compilation fail 
                .V((byte)1).V('2').V(4u).V(8L)
                .V((byte)1).V('2').V(8L).V(4u)
                .V((byte)1).V(8L).V('2').V(4u)
                .V(8L).V((byte)1).V('2').V(4u)
                .V(8L).V((byte)1).V(4u).V('2')
                .V((byte)1).V(8L).V(4u).V('2')
                .V((byte)1).V(4u).V(8L).V('2')
                .V((byte)1).V(4u).V('2').V(8L)
                .V(4u).V((byte)1).V('2').V(8L)
                .V(4u).V((byte)1).V(8L).V('2')
                .V(4u).V(8L).V((byte)1).V('2')
                .V(8L).V(4u).V((byte)1).V('2')
                .V(8L).V(4u).V('9').V((byte)1)
                .V(4u).V(8L).V('2').V((byte)1)
                .V(4u).V('2').V(8L).V((byte)1)
                .V(4u).V('2').V((byte)1).V(8L)
                .V('2').V(4u).V((byte)1).V(8L)
                .V('2').V(4u).V(8L).V((byte)1)
                .V('2').V(8L).V(4u).V((byte)1)
                .V(8L).V('2').V(4u).V((byte)1)
                .V(8L).V('2').V((byte)1).V(4u)
                .V('2').V(8L).V((byte)1).V(4u)
                .V('2').V((byte)1).V(8L).V(4u)
                .V('7').V((byte)1).V(4u).V(8L);
    
            var args = crazy as TSource;
    
            if(null!=args) {
                f(args);
            }
        }
    
        [TestMethod]
        public void TestMethod() {
            Func<
                T<byte>.T<char>.T<uint>.T<long>.
                T<byte>.T<char>.T<long>.T<uint>.
                T<byte>.T<long>.T<char>.T<uint>.
                T<long>.T<byte>.T<char>.T<uint>.
                T<long>.T<byte>.T<uint>.T<char>.
                T<byte>.T<long>.T<uint>.T<char>.
                T<byte>.T<uint>.T<long>.T<char>.
                T<byte>.T<uint>.T<char>.T<long>.
                T<uint>.T<byte>.T<char>.T<long>.
                T<uint>.T<byte>.T<long>.T<char>.
                T<uint>.T<long>.T<byte>.T<char>.
                T<long>.T<uint>.T<byte>.T<char>.
                T<long>.T<uint>.T<char>.T<byte>.
                T<uint>.T<long>.T<char>.T<byte>.
                T<uint>.T<char>.T<long>.T<byte>.
                T<uint>.T<char>.T<byte>.T<long>.
                T<char>.T<uint>.T<byte>.T<long>.
                T<char>.T<uint>.T<long>.T<byte>.
                T<char>.T<long>.T<uint>.T<byte>.
                T<long>.T<char>.T<uint>.T<byte>.
                T<long>.T<char>.T<byte>.T<uint>.
                T<char>.T<long>.T<byte>.T<uint>.
                T<char>.T<byte>.T<long>.T<uint>.
                T<char>.T<byte>.T<uint>.T<long>, String>
            f = args => {
                Debug.WriteLine(String.Format("Length={0}", args.GetLength()));
    
                // print fourth value from the last
                Debug.WriteLine(String.Format("value={0}", args.Next.Next.Next.Value));
    
                args.Next.Next.Next.Value='x';
                Debug.WriteLine(String.Format("value={0}", args.Next.Next.Next.Value));
    
                return "test";
            };
    
            MyMethod(f);
        }
    }
    

注意すべきもう 1 つのことはT、ネストされていない という名前の2 つのクラスがあることTです。

public class T<P>:C<P> {

使用の一貫性のためだけであり、C直接new編集されないようにクラスを抽象化しました。

上記のコード部分では、一般的な引数を展開して長さを計算する必要があります。使用した 2 つの拡張メソッドを次に示します。

  • コード(拡張子)

    using System.Diagnostics;
    using System;
    
    namespace VariadicGenerics {
        [DebuggerStepThrough]
        public static class Extensions {
            public static readonly Type VariadicType = typeof(C<>.T<>);
    
            public static bool TypeIs(this Type x, Type d) {
                if(null==d) {
                    return false;
                }
    
                for(var c = x; null!=c; c=c.BaseType) {
                    var a = c.GetInterfaces();
    
                    for(var i = a.Length; i-->=0;) {
                        var t = i<0 ? c : a[i];
    
                        if(t==d||t.IsGenericType&&t.GetGenericTypeDefinition()==d) {
                            return true;
                        }
                    }
                }
    
                return false;
            }
    
            public static Type[] GetExpandedGenericArguments(this Type t) {
                var expanded = new Type[] { };
    
                for(var skip = 1; t.TypeIs(VariadicType) ? true : skip-->0;) {
                    var args = skip>0 ? t.GetGenericArguments() : new[] { t };
    
                    if(args.Length>0) {
                        var length = args.Length-skip;
                        var temp = new Type[length+expanded.Length];
                        Array.Copy(args, skip, temp, 0, length);
                        Array.Copy(expanded, 0, temp, length, expanded.Length);
                        expanded=temp;
                        t=args[0];
                    }
                }
    
                return expanded;
            }
        }
    }
    

この実装では、コンパイル時の型チェックを中断しないことを選択したparams object[]ため、値を提供するような署名を持つコンストラクターまたはファクトリはありません。代わりに、V大量のオブジェクトのインスタンス化にメソッドの流暢なパターンを使用して、型を可能な限り静的に型チェックできるようにします。

于 2017-12-23T01:16:56.760 に答える