Zev Spitz
Zev Spitz

Reputation: 15357

Detect indexer from MethodInfo

Given the following code:

var dict = new Dictionary<string,string>() {
    {"",""}
};
Expression<Func<string>> expr = () => dict[""];

expr.Body returns an instance of MethodCallExpression, whose Method property returns the get__Item MethodInfo.

There doesn't seem to be any information which I can use to detect that the method being called (get__Item) is the method underlying an indexer.

How can I detect that a given MethodInfo refers to the method underlying an indexer?

This is not a duplicate of Indentifying a custom indexer using reflection in C#, because (as noted in the title, the comments, and this answer) I don't have a PropertyInfo, only a MethodInfo; and the linked question is asking about identifying a specific PropertyInfo as the indexer.

I am trying to map expression trees to Roslyn SyntaxNodes, and the above expression tree should not be mapped as:

() => dict.Item("")

or:

() => dict.get__Item("")

but rather, as the original source code:

() => dict[""]

Upvotes: -1

Views: 395

Answers (1)

Scott Hannen
Scott Hannen

Reputation: 29252

Update: I can't delete this because it's the accepted answer. It worked for someone but I'd check out the other answer.

You can determine which property (if any) is an indexer by inspecting the type. (You're looking at a method, not a property, but I'll get to that.)

From the DefaultMemberAttribute reference

The C# compiler emits the DefaultMemberAttribute on any type containing an indexer.

So the question becomes

  • does the type on which the method is invoked have that attribute?
  • is the method you're inspecting either the getter or setter for that property?

If the answer to both is "yes" then the method accesses an indexer property.

Here are a few functions. This isn't pretty. I'm not questioning whether or not your reason makes sense. I just found it interesting.

public static class ReflectionExtensions
{
    public static bool IsIndexerPropertyMethod(this MethodInfo method)
    {
        var declaringType = method.DeclaringType;
        if (declaringType is null) return false;
        var indexerProperty = GetIndexerProperty(method.DeclaringType);
        if (indexerProperty is null) return false;
        return method == indexerProperty.GetMethod || method == indexerProperty.SetMethod;
    }

    private static PropertyInfo GetIndexerProperty(this Type type)
    {
        var defaultPropertyAttribute = type.GetCustomAttributes<DefaultMemberAttribute>()
            .FirstOrDefault();
        if (defaultPropertyAttribute is null) return null;
        return type.GetProperty(defaultPropertyAttribute.MemberName, 
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    }
}

These are not the most glorious unit tests, but I like to include them anyway.

[TestClass]
public class ReflectionExtensionTests
{
    [TestMethod]
    public void DetectsIndexer()
    {
        var dict = new Dictionary<string, string>() {
            {"",""}
        };
        Expression<Func<string>> expr = () => dict[""];
        var method = (expr.Body as MethodCallExpression).Method;
        Assert.IsTrue(method.IsIndexerPropertyMethod());
    }

    [TestMethod]
    public void DetectsNotIndexer()
    {
        var dict = new Dictionary<string, string>() {
            {"",""}
        };
        Expression<Action<string, string>> expr = (s, s1) => dict.Add(s, s1);
        var method = (expr.Body as MethodCallExpression).Method;
        Assert.IsFalse(method.IsIndexerPropertyMethod());
    }

    [TestMethod]
    public void DetectsRenamedIndexer()
    {
        var myClass = new ClassWithRenamedIndexer();
        Expression<Func<int>> expr = () => myClass[2];
        var method = (expr.Body as MethodCallExpression).Method;
        Assert.IsTrue(method.IsIndexerPropertyMethod());
    }

    class ClassWithRenamedIndexer
    {
        [IndexerName("blarg")]
        public int this[int index]    // Indexer declaration  
        {
            get { return 1; }
        }
    }
}

Upvotes: -1

Related Questions