Динамический доступ к List <T>.Count создает исключение RuntimeBinderException, когда T является закрытым или защищенным классом. - PullRequest
0 голосов
/ 25 июня 2018

Я писал метод определения размера IEnumerable без его перечисления, вот что у меня есть:

internal static class Extensions
{
    internal delegate int GetLength(dynamic obj);

    internal static readonly GetLength ByCount = o => o.Count;

    internal static readonly GetLength ByLength = o => o.Length;

    internal static readonly IDictionary<Type, GetLength> EnumerationSafeLengthGetters = new Dictionary<Type, GetLength>
    {
        { typeof(Array),                 ByLength },
        { typeof(ICollection),           ByCount  },
        { typeof(ICollection<>),         ByCount  }, // ICollection<T> doesn't extend ICollection
        { typeof(IDictionary<,>),        ByCount  },
        { typeof(IReadOnlyCollection<>), ByCount  }, // Matches System.Collections.Concurrent.ConcurrentStack`1 and System.Collections.Concurrent.ConcurrentQueue`1
        { typeof(string),                ByLength }
    };

    internal static int GetEnumerationSafeLength(this IEnumerable values)
    {
        Type type = values.GetType();
        return  EnumerationSafeLengthGetters.FirstOrDefault(g => g.Key.IsAssignableFrom(type)).Value?.Invoke((dynamic) values) ?? -1;
    }
}

Я начал получать странные исключения RuntimeBinderException в моих тестовых методах иНаконец-то я отследил это, связавшись с модификатором доступа для содержащегося типа:

public class PublicType { }
protected internal class ProtectedInternalType { }
protected class ProtectedType { }
internal class InternalType { }
private class PrivateType { }

public static void Main(params string[] args)
{
    Console.WriteLine(new List<PublicType>()           .GetEnumerationSafeLength()); // Prints "0"
    Console.WriteLine(new List<ProtectedInternalType>().GetEnumerationSafeLength()); // Prints "0"
    Console.WriteLine(new List<InternalType>()         .GetEnumerationSafeLength()); // Prints "0"

    // Both throw: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : 'object' does not contain a definition for 'Count'
    Console.WriteLine(new List<ProtectedType>().GetEnumerationSafeLength()); 
    Console.WriteLine(new List<PrivateType>()  .GetEnumerationSafeLength());
}

Полное исключение, выдаваемое в случаях, когда содержащийся тип равен protected или private:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : 'object' does not contain a definition for 'Count'
   at CallSite.Target(Closure , CallSite , Object )
   at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
   at CallSite.Target(Closure , CallSite , Object )
   at Test.Extensions.<>c.<.cctor>b__5_0(Object o) in C:\Users\mrlore\Documents\Visual Studio 2015\Projects\Test\Test\Program.cs:line 13
   at CallSite.Target(Closure , CallSite , GetLength , Object )
   at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
   at CallSite.Target(Closure , CallSite , GetLength , Object )
   at Test.Extensions.GetEnumerationSafeLength(IEnumerable values) in C:\Users\mrlore\Documents\Visual Studio 2015\Projects\Test\Test\Program.cs:line 30
   at Test.Test.Main(String[] args) in C:\Users\mrlore\Documents\Visual Studio 2015\Projects\Test\Test\Program.cs:line 50
   at Test.Test.CallMain() in C:\Users\mrlore\Documents\Visual Studio 2015\Projects\Test\Test\Program.cs:line 55

Что я могу с этим сделать?Это ошибка?Я знаю, что dynamic нельзя использовать для доступа к частным свойствам, но List<T>.Count является общедоступным, так почему я не могу получить к нему доступ?Есть ли другой способ сделать это?

1 Ответ

0 голосов
/ 25 июня 2018

Попробуйте это

public static readonly GetLength ByCount = o =>
{
    var type = o.GetType();
    var prop = type.GetProperty("Count");
    object p = prop.GetValue(o, null);

    return (int)p;
};

public static readonly GetLength ByLength = o =>
{
    var type = o.GetType();
    var prop = type.GetProperty("Length");
    object p = prop.GetValue(o, null);

    return (int)p;
};

Когда вы используете динамический объект, приведение свойств происходит в первую очередь во время выполнения и из-за того, что тип не доступен в Extensions.cs, возникает исключение

...