3

式内でインデクサーが発生するタイミングをプログラムで認識する必要がありますが、結果の式ツリーは期待したものではありません。

class IndexedPropertiesTest
{
    static void Main( string[] args ) { new IndexedPropertiesTest(); }

    public string this[int index]
    {
        get { return list[index]; }
        set { list[index] = value; }
    }
    List<string> list = new List<string>();

    public IndexedPropertiesTest()
    {
        Test( () => this[0] );
    }

    void Test( Expression<Func<string>> expression )
    {
        var nodeType = expression.Body.NodeType;
        var methodName = ((MethodCallExpression)expression.Body).Method.Name;
    }
}

上記のコードで、nodeTypeは「Call」で、methodNameは「get_Item」です。なんで?expression.Bodyと同等であってはなりませんExpression.Property( Expression.Constant( this ), "Item", Expression.Constant( 0 ) )か? それが私が期待したことです。

ほぼすべての式が与えられた場合に、非常に一般的な方法でインデクサーを検出する機能が必要です。意図された式ツリーのこのマングリングは、それを行う私の能力を危うくします。メソッド名が「get_Item」であることに依存するのは、あまりにも脆すぎます。さらに、IndexerNameAttributeとにかくインデクサー プロパティの名前を変更するために使用された可能性があります。

とにかく、コンパイラに意図した式ツリーを生成させる方法はありますか? 式を手動で作成することはオプションではないため、提案しないでください。または、私が持っているものがインデクサーであることをプログラムで確認する方法はありますか?

4

2 に答える 2

3

あなたの答えがあると思います: それが C# コンパイラの仕組みです。

あなたのコードを VB.NET に翻訳しました。役に立たないことに、VB.NET は methodget_Itemを呼び出さず、指定した名前で呼び出します。以下の例では、最終的にget_MyDefaultProperty.

Sub Main
    Dim x as IndexedPropertiesTest = New IndexedPropertiesTest()

End Sub

' Define other methods and classes here
Class IndexedPropertiesTest

    Private list as New List(Of String) From { "a" }
    Default Property MyDefaultProperty(index as Integer) as String
        Get
            Return list(index)
        End Get
        Set(value as String)
            list(index) = value
        End Set
    End Property

    Public Sub New
        Test( Function() Me(0))
    End Sub

    Public Sub Test(expression as Expression(Of Func(Of String)))

        Dim nodeType as ExpressionType = expression.Body.NodeType
        Dim methodName as String = CType(expression.Body, MethodCallExpression).Method.Name

        'expression.Dump() 'Using LINQPad

    End Sub

End Class

ただし、すべてが失われるわけではありません。Visitor を記述して、get_Item呼び出しをIndexExpression. 私はここでそれを始めました:

public class PropertyFixerVisitor : ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.Name.StartsWith("get_"))
        {
            var possibleProperty = node.Method.Name.Substring(4);
            var properties = node.Method.DeclaringType.GetProperties()
                .Where(p => p.Name == possibleProperty);

            //HACK: need to filter out for overriden properties, multiple parameter choices, etc.
            var property = properties.FirstOrDefault();
            if (property != null)
                return Expression.Property(node.Object, possibleProperty, node.Arguments.ToArray());
            return base.VisitMethodCall(node);

        }
        else
            return base.VisitMethodCall(node);
    }
}

その後、次のように Test メソッドを安全に変更できます。

void Test(Expression<Func<string>> expression)
{
    var visitor = new PropertyFixerVisitor();
    var modExpr = (Expression<Func<string>>)visitor.Visit(expression);

    var indexExpression = (modExpr.Body as IndexExpression); //Not Null
}
于 2015-07-14T22:25:39.267 に答える
0

私は最近同じ問題に直面し、最終的に次の解決策になりました(例を挙げて):

void Test(Expression<Func<string>> expression)
{
    if (expression.Body.NodeType == ExpressionType.Call)
    {
        var callExpression = (MethodCallExpression)expression.Body;
        var getMethod = callExpression.Method;
        var indexer = getMethod.DeclaringType.GetProperties()
            .FirstOrDefault(p => p.GetGetMethod() == getMethod);
        if (indexer == null)
        {
            // Not indexer access
        }
        else
        {
            // indexer is a PropertyInfo accessed by expression
        }
    }
}

したがって、基本的に、インデクサーに特定の方法で名前を付けることに依存する代わりに、次の方法に依存します。

  1. の 2 つのオブジェクトはMethodInfo、等しい (または等しくない) と比較できます (operator ==operator !=は両方とも に実装されていMethodInfoます)。
  2. インデクサーには get メソッドがあります。一般に、プロパティには get メソッドがない場合がありますが、そのようなプロパティを使用してラムダ構文を使用した式を最初から作成することはできません。
  3. MemberExpression代わりに、非インデクサー プロパティへのアクセスが得られますMethodCallExpression(そうでない場合でもPropertyInfo、すべてのインデクサーには少なくとも 1 つのパラメーターがあるため、単純なプロパティを表すことは、GetIndexParameters メソッドを使用してインデクサーを表すものと常に区別できます)。

このアプローチは、クラス内にインデクサー exst の複数のオーバーロードがある場合にも機能します。これは、それぞれに独自のオーバーロードがあり、MethodInfo1 つだけが式で使用されるものと等しくなるためです。

: 上記の方法は、プライベート インデクサーでも、プライベート get メソッドを使用するインデクサーでも機能しません。アプローチを一般化するには、 と の正しいオーバーロードを使用する必要がGetPropertiesありGetGetMethodます。

// ...
var indexer = getMethod.DeclaringType.GetProperties(
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
    .FirstOrDefault(p => p.GetGetMethod(nonPublic: true) == getMethod);
// ...

それが誰かを助けることを願っています。

于 2018-12-12T14:37:50.340 に答える