Wednesday, September 30, 2009

Custom MS Build Task to tame System.Reflection.AmbiguousMatchException: Ambiguous match found.

Update (Oct 3, 2009): Thanks to the reader that pointed out that the MS Build task was using a hard-coded path. The example has been corrected to use a parameterized path that relies on the MSBuildProjectDirectory [3] reserved property.

An innocent “mistake” [1], if you can even call it that, has been punishing developers worldwide for a good part of the last decade – can you spot it?

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>
   3: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
   5: <html xmlns="" >
   6: <head runat="server">
   7:     <title></title>
   8: </head>
   9: <body>
  10:     <form id="form1" runat="server">
  11:     <div>
  12:         <asp:HiddenField runat="server" ID="RecordId" />
  13:     </div>
  14:     </form>
  15: </body>
  16: </html>
   1: using System;
   3: namespace WebApplication1
   4: {
   5:     public partial class _Default : System.Web.UI.Page
   6:     {
   7:         private int recordId = 0;
   9:         protected void Page_Load(object sender, EventArgs e)
  10:         {
  11:             Response.Write(recordId);
  12:         }
  13:     }
  14: }

What’s worse is that the exception thrown doesn’t lend any clues as to what the source of the ambiguity is – even this level of detail requires some custom logging:

System.Reflection.AmbiguousMatchException: Ambiguous match found.
   at System.RuntimeType.GetField(String name, BindingFlags bindingAttr)
   at System.Web.UI.Util.GetNonPrivateFieldType(Type classType, String fieldName)
   at System.Web.Compilation.BaseTemplateCodeDomTreeGenerator.BuildFieldDeclaration
ControlBuilder builder)

So what’s wrong with the code?

Two fields differ only in case. Yup – that’s it. RecordId is both a HiddenField and an integer. C# allows this – not a word is raised during compilation. Based on what I’ve seen and read (it’s widely covered), it only happens only when you pre-compile either a Website or Web application and navigate to the page in question.

Now imagine having a page with 20+ controls and trying to figure out where this is coming from – how’s the reflector supposed to help here?

How can we catch it at compile time?

It’s surprisingly easy, actually:


Until it gets fixed – and yes, it is a defect – here’s a custom task (MemberCaseTask) that you can add to your projects to catch it at build-time as opposed to already-in-staging-and-didn’t-think-to-navigate-to-each-view-after-precompilation-time.

Reference the custom Task in the project/solution script as follows:

   1: <UsingTask TaskName="MemberCaseTask"
   2:            AssemblyFile="C:\Users\admin\Documents\Visual Studio 2008\Projects\ClassLibrary1\ClassLibrary1\bin\Debug\ClassLibrary1.dll" /> 
   3:  <Target Name="AfterBuild">
   4:      <MemberCaseTask AssemblyPath="$(MSBuildProjectDirectory)\bin" />
   5:  </Target>  

Build this file and update the AssemblyFile reference above (note that we copy the original definition of GetNonPrivateFieldType from System.Web.UI.Util) :

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Reflection;
   6: using System.IO;
   7: using Microsoft.Build.Framework;
   8: using Microsoft.Build.Utilities;
  10: namespace ClassLibrary1
  11: {
  12:     public class MemberCaseTask: Task
  13:     {
  14:         [Required]
  15:         public string AssemblyPath { get; set; }
  17:         public override bool Execute()
  18:         {
  19:             foreach (Assembly assembly in GetAllAssemblies())
  20:             {
  21:                 try
  22:                 {
  23:                     foreach (Type type in assembly.GetTypes())
  24:                         foreach (FieldInfo field in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
  25:                             try
  26:                             {
  27:                                 GetNonPrivateFieldType(type, field.Name);
  28:                             }
  29:                             catch (Exception)
  30:                             {
  31:                                 Log.LogError(string.Format("{0} has a field conflict on field {1}.", type.Name, field.Name));
  32:                                 return false;
  33:                             }
  34:                 }
  35:                 catch (System.Reflection.ReflectionTypeLoadException)
  36:                 { }
  37:             }
  39:             return true;
  40:         }
  42:         private IEnumerable<Assembly> GetAllAssemblies()
  43:         {
  44:             foreach (FileInfo file in new DirectoryInfo(AssemblyPath).GetFiles("*.dll", SearchOption.TopDirectoryOnly))
  45:             {
  46:                 Assembly assembly = AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(file.FullName));
  47:                 yield return assembly;
  48:             }
  49:         }
  51:         static Type GetNonPrivateFieldType(Type classType, string fieldName)
  52:         {
  53:             FieldInfo field = classType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
  54:             if ((field != null) && !field.IsPrivate)
  55:             {
  56:                 return field.FieldType;
  57:             }
  58:             return null;
  59:         }
  60:     }
  61: }


[1] - System.Reflection.AmbiguousMatchException: Ambiguous match found

[2] - How to Write a Task:

[3] - MSBuild Reserved Properties:


Rahul Jain said...


Can u please Share Your Sample Project With Us.


north face outlet said...

Thank you for the informarion! nice post!sites are creating whole new ways for users to share and gain information.

WiseSolomon said...

how do you do this for an old web application? Create a custom task ?

John Grabanski said...

Here's a link to an MSDN article regarding custom tasks for MSBuild.

Hope this helps.

Anonymous said...

Thanks a ton for this fix, you're a rock star in my book!

Julio Negron said...

This code works great but it's locking the bin folder of the target project, preventing me from building my project. Any ideas why? I'm targeting an 4.5 web application using VS2013 Ultimate.

justin adams said...

We have an inconceivable experience here and tight living up to expectations calendar is not an obstacle for us. We will satisfy the greater part of your particulars and goals paying little mind to what your due date term is here write my essay.

justin adams said...

Custom essay is one quite essay formally called formal or tradition essay. college papers offer you varied varieties of custom essays and that we feel terribly proud to supply it for our shopper.

Noki said...

Wow, that was pretty interesting. Inspiring, as well. Thanks for sharing such inspiring experience with us. tas louis vuitton

Sagheni Sagheni said...

vimax asli
vimax canada
pembesar penis
obat pembesar penis
obat perangsang wanita
obat sipilis
obat perangsang wanita

marko said...

The advance sum alongside interest must be cleared inside of the residency according to the agreement. The loan specialist can take ownership of the auto at long last, if the borrower can't or does not pay back the aggregate sum in time. The bank, before making such a radical stride, for the most part reminds and cautions the concerned borrower a few times. Cash Advance