21

To avoid old-fashioned non-generic syntax when searching for attributes of a known type, one usually uses the extension methods in System.Reflection.CustomAttributeExtensions class (since .NET 4.5).

However this appears to fail if you search for an attribute on the return parameter of an overridden method (or an accessor of an overridden property/indexer).

I am experiencing this with .NET 4.6.1.

Simple reproduction (complete):

using System;
using System.Reflection;

namespace ReflectionTrouble
{
  class B
  {
    //[return: MyMark("In base class")] // uncommenting does not help
    public virtual int M() => 0;
  }

  class C : B
  {
    [return: MyMark("In inheriting class")] // commenting away attribute does not help
    public override int M() => -1;
  }

  [AttributeUsage(AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = false)] // commenting away AttributeUsage does not help
  sealed class MyMarkAttribute : Attribute
  {
    public string Descr { get; }

    public MyMarkAttribute(string descr)
    {
      Descr = descr;
    }

    public override string ToString() => $"MyMark({Descr})";
  }

  static class Program
  {
    static void Main()
    {
      var derivedReturnVal = typeof(C).GetMethod("M").ReturnParameter;

      // usual new generic syntax (extension method in System.Refelction namespace):
      var attr = derivedReturnVal.GetCustomAttribute<MyMarkAttribute>(); // BLOWS UP HERE, System.IndexOutOfRangeException: Index was outside the bounds of the array.
      Console.WriteLine(attr);

      // old non-generic syntax without extension method works:
      var attr2 = ((MyMarkAttribute[])(derivedReturnVal.GetCustomAttributes(typeof(MyMarkAttribute), false)))[0]; // OK
      Console.WriteLine(attr2);
    }
  }
}

The code may look "too long to read", but it is really just an overridden method with an attribute on its return parameter and the obvious attempt to retrieve that attribute instance.

Stack trace:

Unhandled Exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at System.Attribute.GetParentDefinition(ParameterInfo param)
   at System.Attribute.InternalParamGetCustomAttributes(ParameterInfo param, Type type, Boolean inherit)
   at System.Attribute.GetCustomAttributes(ParameterInfo element, Type attributeType, Boolean inherit)
   at System.Attribute.GetCustomAttribute(ParameterInfo element, Type attributeType, Boolean inherit)
   at System.Reflection.CustomAttributeExtensions.GetCustomAttribute[T](ParameterInfo element)
   at ReflectionTrouble.Program.Main() in c:\MyPath\Program.cs:line 38

Am I doing anything obviously wrong?

Is this a bug, and if yes, is it well-known, and is it an old bug?

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • have you noticed that you are trying to get the custom attribute of the return parameter and not of the method itself? `var derivedReturnVal = typeof(C).GetMethod("M").ReturnParameter;` – shirbr510 Aug 04 '16 at 05:56
  • also, the attribute should be written as: `[MyMark("In inheriting class")]` (the `return:` is redundant) – shirbr510 Aug 04 '16 at 05:58
  • 1
    @shirbr510 The attribute is set to target return types, so the `return:` is needed. As the attribute is applied to the return value - not the method itself - the code makes sense. – Will Ray Aug 04 '16 at 06:12
  • @shirbr510 The question is about attributes on the return parameter. I tried to make that very clear in both the text and the title. Therefore my `.ReturnParameter` is intentional. Also `[return: ...]` is needed because `method:` is the default "location" for that attribute position. – Jeppe Stig Nielsen Aug 04 '16 at 08:31
  • I've run into what I believe is this same issue in .Net Core 3 using Entity Framework inside a Xamarin App. I've traced it down to the same problem on getting custom attributes on a base property. Was this ever fixed? I ended up opening an issue on the Mono project that hasn't got any attention yet: https://github.com/mono/mono/issues/17477 – Steve Nov 06 '19 at 06:28

1 Answers1

6

This indeed looks like a bug. The problem seems to be with the inheritance. This does work:

ReturnParameter.GetCustomAttribute<MyMark>(inherit: false)

Retrieving attributes have two code paths that work a bit differently: MemberInfo.GetCustomAttribute (older) and Attribute.GetCustomAttribute (newer and recommended). There are also the generic extension methods, which use the latter, newer approach. The difference between these is indeed the way they handle inheritance. .NET 1.0 ignored the inherit parameter for properties, events and parameters. So in order not to break things, the static methods on Attribute we introduced in .NET 2.0 (together with this bug).

Seems like they neglected to special-case the return value parameter when going up the inheritance tree (see here). You can open an issue in the GitHub repo or a Connect bug.

Eli Arbel
  • 22,391
  • 3
  • 45
  • 71
  • 1
    I think you are right. The call `Attribute.GetCustomAttribute(typeof(C).GetMethod("M").ReturnParameter, typeof(MyMarkAttribute))` also encounters the bug. If you are correct this bug was introduced in .NET 2.0 (more than ten years ago, at the time of Visual Studio 2005), it should be possible to find bug reports on this. – Jeppe Stig Nielsen Aug 04 '16 at 08:46
  • Now I opened an issue in GitHub as you suggested, see [coreclr/issues/6600 there](https://github.com/dotnet/coreclr/issues/6600). – Jeppe Stig Nielsen Aug 04 '16 at 14:23