DamianEdwards / MiniValidation

A minimalist validation library for .NET built atop the existing features in `System.ComponentModel.DataAnnotations` namespace

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

AmbiguousMatchException if record type has more than one (inherited) Deconstruct method

thoemmi opened this issue · comments

I git a AmbiguousMatchException when calling MiniValidator.TryValidate for my object. I took me some time to strip it down to a minimal repo.

// code
var thingToValidate = new DerivedType(new Dictionary<string, string>());
var isValid = MiniValidator.TryValidate(thingToValidate, out var errors);

// types
public abstract record BaseType(string Type);
public record DerivedType(Dictionary<string, string> Fields) : BaseType("derived");

The error is

System.Reflection.AmbiguousMatchException
Ambiguous match found.
   at System.RuntimeType.GetMethodImplCommon(String name, Int32 genericParameterCount, BindingFlags bindingAttr, Binder binder, CallingConventions callConv, Type[] types, ParameterModifier[] modifiers)
   at System.Type.GetMethod(String name, BindingFlags bindingAttr)
   at MiniValidation.TypeDetailsCache.Visit(Type type, HashSet`1 visited, Boolean& requiresAsync) in C:\Users\Thomas\source\repos\MiniValidation\src\MiniValidation\TypeDetailsCache.cs:line 56
   at MiniValidation.TypeDetailsCache.Visit(Type type) in C:\Users\Thomas\source\repos\MiniValidation\src\MiniValidation\TypeDetailsCache.cs:line 34
   at MiniValidation.TypeDetailsCache.Get(Type type) in C:\Users\Thomas\source\repos\MiniValidation\src\MiniValidation\TypeDetailsCache.cs:line 24
   at MiniValidation.MiniValidator.RequiresValidation(Type targetType, Boolean recurse) in C:\Users\Thomas\source\repos\MiniValidation\src\MiniValidation\MiniValidator.cs:line 44
   at MiniValidation.MiniValidator.TryValidate[TTarget](TTarget target, Boolean recurse, Boolean allowAsync, IDictionary`2& errors) in C:\Users\Thomas\source\repos\MiniValidation\src\MiniValidation\MiniValidator.cs:line 104
   at MiniValidation.MiniValidator.TryValidate[TTarget](TTarget target, IDictionary`2& errors) in C:\Users\Thomas\source\repos\MiniValidation\src\MiniValidation\MiniValidator.cs:line 64
   at MiniValidation.UnitTests.TryValidate.MyTest() in C:\Users\Thomas\source\repos\MiniValidation\tests\MiniValidation.UnitTests\TryValidate.cs:line 332
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)

The exception is thrown in TypeDetailsCache.cs, line 56:

if (type.GetMethod("Deconstruct") is { } deconstruct)

This means there is more than one method named Deconstruct. I've put a breakpoint in that line and called GetMethods("Deconstruct") in the "Immediate Window":

type.GetMethods().Where(m => m.Name == "Deconstruct")
{System.Linq.Enumerable.WhereArrayIterator<System.Reflection.MethodInfo>}
    [0]: {Void Deconstruct(System.Collections.Generic.Dictionary`2[System.String,System.String] ByRef)}
    [1]: {Void Deconstruct(System.String ByRef)}

Apparently there are two Deconstruct methods, one from the base record, and one from the derived record.

Maybe adding BindingFlags.DeclaredOnly as a second parameter to GetMethod might help, but I did not fully understand the implications yet.