Search code examples
c#.netserializationsgen

SGEN fails on where clause of public generic method


I have a project where SGEN is used to precompile the serialization assemblies (without the /proxytypes flag). In this project lives a class that up until now has been internal (thus sgen would leave it alone). I need to make the class public, and the simple act of doing so causes sgen to fail with the error:

The type 'Infragistics.Shared.DisposableObject' is defined in an assembly that is not referenced...'

This assembly is indeed referenced, and always has, else the assembly itself would not compile. The versions also match exactly, and the reference has specific version turned off.

The first confusing part is that this class has no public state (no public fields, no properties at all), which doesn't make it a good candidate for serialization.

The more confusing part is that removing the where clause on the one-and-only public (generic) method allows sgen to process the assembly just fine.

Here is the class with the only public, non-explicitly implemented thing on it (there are two methods implemented for the interface, not relevant):

public class AppointmentDrawFilter : IUIElementDrawFilter
{
    // This is a fluent method to register a type with its handler
    public AppointmentDrawFilter Support<TUiElement>(DrawPhase phases, Func<UIElement, Appointment> retriever = null)
        where TUiElement : UIElement  // <-- commenting out (or changing UIElement to MarshalByRefObject) fixes sgen
    {
        // adds input to a couple dictionaries (failure still occurs with body commented out)
        return this;
    }
}

Notes: UIElement inherits from DisposableObject, the type that sgen can't find when it is failing. Notice that when I comment out the where clause, UIElement is still being used elsewhere, but sgen is not unhappy about it. Marking the Support() method internal also allows sgen to complete, since it only cares about public stuff.

Why would sgen care about a non-webservices public method in the first place?

Why would it only trip up on the existence of the where clause?

Why is the assembly not being found by sgen in this particular case when it is clearly there?


Solution

  • I did some testing with this and found a possible workaround that you may be able to use.

    I found that this could be reproduced when you have a generic method that has a a where clause that references a type (or a base type of the type referenced by the where clause) from an assembly that that you don't have any types derived from in the current assembly.

    For example the simplest reproduction of the issue that I found is:

    Class Library One with the following C# code:

    namespace One
    {
        public class BaseObject
        {
        }
    }
    

    Class Library Two with the following C# code:

    using One;
    
    namespace Two
    {
        public class TestClass
        {
            public void TestMethod<T>()
                where T : BaseObject
            {
    
            }        
        }
    }
    

    When using sgen on Two.dll the error is reproduced that it complains about assembly One not being referenced.

    For a workaround I found that I could derive any class in assembly (Class Library) Two from a class in assembly One or implement an interface from assembly One. To workaround this in your specific case you could add the following class:

    using Infragistics.Shared;
    namespace DayViewTextCenter
    {
        public class WorkaroundSgenIssue:DisposableObject
        {
            protected override void OnDispose()
            {
            }
        }
    }
    

    While I didn't actually find answers to three questions that you asked, I did find that the actual error is a System.InvalidOperationException in the Compile method of the System.Xml.Serialization.Compiler class:

    System.InvalidOperationException occurred
      Message=Unable to generate a temporary class (result=1).
    error CS0012: The type 'One.BaseObject' is defined in an assembly that is not referenced. You must add a reference to assembly 'One, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
    
      Source=System.Xml
      StackTrace:
           at System.Xml.Serialization.Compiler.Compile(Assembly parent, String ns, XmlSerializerCompilerParameters xmlParameters, Evidence evidence)
      InnerException: 
    

    I was able to reproduce this referencing assembly One and Two from above and using the following code:

    Type type = typeof(TestClass);
    Assembly assembly = type.Assembly;
    XmlReflectionImporter importer = new XmlReflectionImporter();
    XmlTypeMapping mapping = importer.ImportTypeMapping(type);
    CompilerParameters parameters = new CompilerParameters();
    Assembly xmlAssembly = XmlSerializer.GenerateSerializer(new Type[] { type }, new XmlMapping[] { mapping }, parameters);
    

    Like the workaround I suggested earlier the exception can be avoided if a second type is added that derives from a class in assembly One.

    Additional class needed for assembly Two:

    public class Test : BaseObject
    {
    }
    

    Updated logic for generating the serialization assembly:

    Type type = typeof(TestClass);
    Assembly assembly = type.Assembly;
    XmlReflectionImporter importer = new XmlReflectionImporter();
    XmlTypeMapping mapping = importer.ImportTypeMapping(type);
    Type type2 = typeof(Test);
    XmlReflectionImporter importer2 = new XmlReflectionImporter();
    XmlTypeMapping mapping2 = importer2.ImportTypeMapping(type2);
    CompilerParameters parameters = new CompilerParameters();
    Assembly xmlAssembly = XmlSerializer.GenerateSerializer(new Type[] { type, type2 }, new XmlMapping[] { mapping, mapping2 }, parameters);