1

I am using FastMember to dynamically read property values of certain types. Something like this:

TypeAccessor accessor = TypeAccessor.Create(type);
return accessor.GetMembers().Where(m => m.CanRead).Select(m => accessor[item, m.Name]);

But I would like to omit indexers. m here is type of Member, but it doesnt seem to have a way to check if Member is an indexer or not.

Is this functionality really not there?

nawfal
  • 70,104
  • 56
  • 326
  • 368
  • @pfx I wouldnt do that.. I am writing a very general purpose library.. You could even have a [different name for indexer](https://stackoverflow.com/questions/5110403/class-with-indexer-and-property-named-item) – nawfal Apr 02 '23 at 05:47
  • Fastmembers Member has very limited information about a member. I would say no. https://github.com/mgravell/fast-member/blob/master/FastMember/MemberSet.cs – Firo Apr 03 '23 at 12:40
  • Is FastMember a constraint, or are you interested in other performant alternatives like [this one](https://github.com/koszeggy/KGySoft.CoreLibraries#alternative-reflection-api)? To avoid shameless self-promotion I will add an answer only on explicit request. – György Kőszeg Apr 05 '23 at 15:27
  • @GyörgyKőszeg There is not constraint as such, but the library you referenced looks too broad for my liking. But still I appreciate an answer. Will also help others in future. – nawfal Apr 05 '23 at 18:35

3 Answers3

2

No. Member type has limited information exposed about the type and is sealed and does not provide ways for extensibility. You can switch completely from using the library and manually cache the reflection or do some workarounds (a "bad" way - use reflection to access Member's private readonly MemberInfo member;). I would suggest a helper class, something like the following:

public static class IndexerHelper
{
    private static readonly ConcurrentDictionary<Type, string?> IndexerNames = new();

    public static bool IsIndexer(Type t, string name)
    {
        var indexerName = GetIndexerName(t);
        return indexerName is not null && indexerName == name;
    }
    
    internal static string? GetIndexerName(Type t)
    {
        if (t is { IsGenericType: true, IsGenericTypeDefinition: false })
        {
            t = t.GetGenericTypeDefinition();
        }
        // TODO - handle arrays?
        return IndexerNames.GetOrAdd(t, type => t.GetProperties()
            .SingleOrDefault(pi => pi.GetIndexParameters().Any())?.Name);
    }
}

And usage:

var type = typeof(Dictionary<int, object>);
TypeAccessor accessor = TypeAccessor.Create(type);

var members = accessor.GetMembers()
    .Where(m => m.CanRead && !IndexerHelper.IsIndexer(type, m.Name))
    .ToList();

Though you will need to pass around instance of Type in addition to the accessor itself.

Also you can fork the library and expose required data and/or create PR to it.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
1

Most pragmatic way for you would be account for a fact that indexer internally is a property with well known name Item and additional parameter. If you work with regular, not obfuscated assemblies, you can filter out members with this name like this:

TypeAccessor accessor = TypeAccessor.Create(type);
return accessor.GetMembers().Where(m => m.CanRead && m.Name != "Item" && !m.GetAttribute(typeof(System.Runtime.CompilerServices.IndexerName), false)).Select(m => accessor[item, m.Name]);

Also Item property is reserved for C# as written in spec. https://learn.microsoft.com/en-Us/dotnet/csharp/language-reference/language-specification/classes#143104-member-names-reserved-for-indexers

Even if you can declare property with name Item, you cannot declare both property Item and indexer in the same class. Try this snippet.

public string Item { get; set; }
public string this[int x] { get => ""; set { } } // error here

This is long time implementation detail and unlikely it will ever change.

And if you at to be precise you may take a look at this PR. https://github.com/mgravell/fast-member/pull/102

codevision
  • 5,165
  • 38
  • 50
  • 1
    I am writing a very general purpose library, where I do not control the shape/signature of the input. Your statement "_Also `Item` property is reserved for C# as written in spec_" is wrong, I can create a type with that property and name and compile it. I can also override the default name of an indexer. – nawfal Apr 04 '23 at 07:39
  • "reserved" in this context means that IF indexer is present it has property name `Item` – Guru Stron Apr 04 '23 at 07:43
  • 1
    There is no problem to declare `Item` property in class without indexer - [see it yourself](https://sharplab.io/#v2:C4LglgNgNAJiDUAfAAgJgIwFgBQyDMABGgQMIEDeOB1RhYAdsAQJLACmAthQOZvADcBAM59+AXxxigA=) – Guru Stron Apr 04 '23 at 07:44
  • 1
    And as is mentioned in the comments to the question you can override the indexer property name with special attribute, so it gets another name. – Guru Stron Apr 04 '23 at 07:45
  • Yeah. I agree. I think you can filter out that using `GetAttribute` method too. – codevision Apr 04 '23 at 14:36
  • @nawfal then probably better not use that library at all. Because if you resort to reflection, then why not do that yourself. It has only simple caching so nothing is lost. – codevision Apr 04 '23 at 14:43
  • 1
    I submit PR. I think that's easier. – codevision Apr 04 '23 at 15:13
1

After clarifying in comments that using FastMember is not a constraint I provide an answer using another library. I understand that you consider it a bit too broad, as its Reflection API is just one of the several other features it offers. Its 'broadness' has a simple reason: this way the library has no 3rd party references at all, but it's still allowed even for its fairly specialized APIs (such as reflection or serialization) to use all of the core components like high performance collections, etc.

As for getting property accessors for the non-indexer properties only, the solution is simple: as PropertyAccessors can be obtained by regular PropertyInfo instances you can simply perform the filtering on the actual properties:

var simpleProperties = type.GetTypeInfo().DeclaredProperties.Where(
    p => p.CanRead // readable properties
    && !p.GetMethod.IsStatic // instance properties
    && p.GetMethod.GetParameters().Length == 0); // non-indexers

// getting the PropertyAccessors for them:
return simpleProperties.Select(pi => PropertyAccessor.GetAccessor(pi));

The approach of getting/setting properties is a bit different. FastMember expects accessing the properties by name, whereas my library provides PropertyInfo-like accessors for each properties. In fact, you can also use the Reflector class if you want to access the properties by name but that class is more about convenience rather than performance.

See this online example for some usage test and comparisons with FastMember.

While creating the example and tried FastMember I realized that both libraries have pros and cons. For example, non-generic access is faster by FastMember than by my library. On the other hand, FastMember supports instance public properties and fields only (even if you specify allowNonPublicAccessors: true), whereas my library can access any kind of members of any visibility, including methods, constructors and static members.

György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • Gyorgy, thank you. But one thing, the whole allure of FastMember is, it uses Reflection.Emit behind the scenes and caches everything internally. In your approach it seems I need to fetch `PropertyInfo` myself to begin with? – nawfal Apr 06 '23 at 07:02
  • FastMember also [fetches](https://github.com/mgravell/fast-member/blob/master/FastMember/TypeAccessor.cs#L308) `PropertyInfos` internally to generate the accessors. And my library uses Reflection.Emit as well if better alternatives are not available (eg. for ref parameters, struct mutators or in case of older targeted frameworks), and the generated accessors are also [cached](https://github.com/koszeggy/KGySoft.CoreLibraries/blob/master/KGySoft.CoreLibraries/Reflection/MemberAccessor.cs#L89) so repeated `PropertyAccessor.GetAccessor(pi)` calls will not regenerate the dynamic delegates. – György Kőszeg Apr 06 '23 at 08:05
  • `PropertyAccessor.GetAccessor(pi)`... My question is where do I get the `pi` from? I guess I have to call ` Type.GetProperties()` myself? – nawfal Apr 07 '23 at 09:28
  • Yes, or look at how I used `simpleProperties` to filter the non-indexer instance properties. I actually used `GetTypeInfo().DeclaredProperties`, which returns also the non-public properties (or you can use the `BindingFlags` as well for filtering). Fortunately these retrievals are cached in the CLR. – György Kőszeg Apr 07 '23 at 10:03