locked
Dynamically loading and instantiating a class that implements a generic interface RRS feed

  • Question

  • I am working on a product line architecture and am running into an issue dynamically creating types that implement generic interfaces.

    I am using C# 2008, .Net 3.5 SP1, and Visual Studio 2008 (version 9.0.30729.1 SP). 

    Deployment

    There are three distinct assemblies under consideration in this solution:

    - Framework.dll: Contains the factory methods that attempt to dynamically create the types as well as the interface definitions implemented by the types

    - BusinessObjects.dll: Contains the business objects for the solutions. This assembly has a reference to Framework.dll to allow business objects to implement interfaces (non-generic interfaces).

    - BusinessIntelligence.dll: Contains the providers that perform work on the business objects. This assembly has references to both Framework.dll to allow implementation of interfaces as well as to BusinessObjects.dll to provide type safety (through generic interface implementations) on types and their procedures. The providers in this assembly implement generic interfaces (defined in Framework.dll) that specify types defined in BusinessObjects.dll.

    All assemblies are co-located on one machine and in the same physical directory.

    Interfaces

    The following non-generic interfaces are defined in Framework.dll

    public interface IBusinessObject {}
    
    public interface ICriteriaObject {}
    and the following generic interfaces are defined in Framework.dll

    public interface IDataAccessComponent<objectType, listType, criteriaType>
         where objectType : IBusinessObject
         where listType : IBusinessObject
         where criteriaType : ICriteriaObject
    {
         objectType Get(criteriaType criteria);
         List<listType> GetList(criteriaType criteria);
         Boolean Delete(criteriaType criteria);
         Boolean Update(objectType businessObject);
    }
    
    
    public interface IBusinessIntelligenceComponent<objectType, listType, criteriaType>
         : IDataAccessComponent<objectType, listType, criteriaType>
         where objectType : IBusinessObject
         where listType : IBusinessObject
         where criteriaType : ICriteriaObject
    {
         Boolean Validate(objectType businessObject, out List<String> errors);
    
         objectType GetNew();
    }
    Business Objects

    I am in proof of concept mode here, so these are very simple business objects. Of course, these are defined in BusinessObjects.dll

    public class BasicBusinessObject : IBusinessObject
    {
         private Int32 _id = 0;
    
         public Int32 Id
         {
              get { return _id; }
              set { _id = value; }
         }
    }
    
    
    public class BasicCriteria : ICriteriaObject
    {
         private Int32 _id = 0;
    
         public Int32 Id
         {
              get { return _id; }
              set { _id = value; }
         }
    }
    Providers

    These providers are defined in BusinessIntelligence.dll

    public class BusinessIntelligenceComponent
         : IBusinessIntelligenceComponent<BasicBusinessObject, BasicBusinessObject, BasicCriteria>
    
    {
         private IDataAccessComponent<BasicBusinessObject, BasicBusinessObject, BasicCriteria> _ dac = null;
    
         public BusinessIntelligenceComponent()
         {
              // This is commented in my current test code to focus debugging
              // _dac = FrameworkFactories.GetDataAccessComponent();
         }
    
         // I'll spare the boring details of the generic interface implementation, but it is all here
         // Most methods are simple pass-throughs to the data access component, some with simple logic applied
    }
    
    

    Factory Methods

    The following Factory Methods are in the Framework.dll assembly.

    public class FrameworkFactories
    {
         private static String assemblyPath = Assembly.GetExecutingAssembly().Location.SubString(0, Assembly.GetExecutingAssembly.Location.LastIndexOf("\\");
    
         public static IBusinessIntelligenceComponent<IBusinessObject, IBusinessObject, ICriteriaObject> GetBusinessIntelligenceComponent()
         {
              // hard-coded just for testing...
              String assemblyName = "BusinessIntelligence.dll"
              String componentTypeName = "BusinessIntelligenceComponent"
    
              String assemblyFullName = System.IO.Path.Combine(assemblyPath, assemblyName);
    
              return (IBusinessIntelligenceComponent<IBusinessObject, IBusinessObject, ICriteriaObject>)Activator.CreateInstanceFrom(assemblyFullName, componentTypeName);
         }
    
    }

    The Tester

    I have a test harness that references BusinessObjects.dll and Framework.dll that has a method that looks like this:
    ...
    
    IBusinessIntelligenceComponent<BasicBusinessObject, BasicBusinessObject, BasicCriteria> _bic = null;
    
    _bic = (IBusinessIntelligenceComponent<BasicBusinessObject, BasicBusinessObject, BasicCriteria>)FrameworkFactories.GetBusinessIntelligenceComponent();
    
    ...
    The Problem

    When the tester executes, it calls into the FrameworkFactories.GetBusinessIntelligenceComponent() method, throwing an exception when it gets to the Activator.CreateInstanceFrom() call.

    The exception details are:

    TypeLoadException: GenericArugments[0] 'BasicBusinessObject' on 'IBusinessIntelligenceComponent`3[objectType,listType,criteriaType]' violates the constraint of type parameter 'objectType'.


    Help!

    What I've tried

    I have tried quite a few things. The first thing I did was to remove the dynamic load from the equation, setting hard references between the assemblies and using _bic = new BusinessIntelligenceComponent() in the factory method, and that worked like a charm; telling me that my generic interfaces are good, their implementation is good, and there is nothing dealing with type-casting getting in my way.

    I did some research and found posts where people used

       Type.GetType(type).MakeGenericType(implementedTypes)

    in similar (but not exact) scenarios.

    I also tried using reflection to get the generic parameters for the type, like

        Type.GetType(type).GetGenericArguments

    To no avail.


    I believe this to be an issue NOT with the dynamic loading, the generic interfaces (their parameters or the types bound to them), or my references as defined. What I *think* is happening is that, when I dynamically load the BusinessIntelligence.dll and attempt to create an instance of the type BusinessIntelligenceComponent, the referenced assemblies are not being loaded as well, making the type definition in the generic interface implementation on the object reference a type that cannot be bound.

    Help!

    What am I missing? 

    Thanks for any and all responses....




    Friday, November 20, 2009 6:44 PM

Answers

  • Is the tester in the same directory as all of the assemblies (including the BusinessIntelligence assembly), or were just the two that were referenced copied to the tester's directory?

    There are issues with the code you've provided, as well, but as far as I can tell they're unrelated to this error. Eliminating those would rule out those possibilities, though.

        - The type you load with Activator.CreateInstanceFrom(), or other type loading mechanisms, must be fully qualified (i.e. BusinessIntelligence.BusinessIntelligenceComponent). It could be that you just haven't enclosed those types in a namespace, though.

        - Calling Activator.CreateInstanceFrom() will return you an ObjectHandle, which you have to call Unwrap() on prior to casting.

        - Attempting to cast to IBusinessIntelligenceComponent<>, specifying the constrained interface (i.e. IBusinessObject) in place of the actual T, will cause an InvalidCastException. Specifying a constrained T on the GetBusinessIntelligenceComponent() method, then casting using that T as the generic parameter, would be a preferable way of dealing with things.

    I've had success with your method, doing the following.

        IBusinessIntelligenceComponent<BasicBusinessObject, BasicBusinessObject, BasicCriteria> _bic = null;
        _bic = FrameworkFactories.GetBusinessIntelligenceComponent<BasicBusinessObject, BasicCriteria>();

    With the method defined like this.

         public static IBusinessIntelligenceComponent<TBusinessObject, TBusinessObject, TCriteriaObject>
                                                        GetBusinessIntelligenceComponent<TBusinessObject, TCriteriaObject>()
                where TBusinessObject: IBusinessObject
                where TCriteriaObject: ICriteriaObject

            {
                // hard-coded just for testing...
                String assemblyName = "BusinessIntelligence.dll";
                String componentTypeName = "BusinessIntelligence.BusinessIntelligenceComponent";

                String assemblyFullName = System.IO.Path.Combine(assemblyPath, assemblyName);

                return (IBusinessIntelligenceComponent<TBusinessObject, TBusinessObject, TCriteriaObject>)Activator.CreateInstanceFrom(assemblyFullName, componentTypeName).Unwrap();
            }

    • Marked as answer by DidacticDan Sunday, November 22, 2009 6:38 AM
    Friday, November 20, 2009 9:39 PM

All replies

  • Is the tester in the same directory as all of the assemblies (including the BusinessIntelligence assembly), or were just the two that were referenced copied to the tester's directory?

    There are issues with the code you've provided, as well, but as far as I can tell they're unrelated to this error. Eliminating those would rule out those possibilities, though.

        - The type you load with Activator.CreateInstanceFrom(), or other type loading mechanisms, must be fully qualified (i.e. BusinessIntelligence.BusinessIntelligenceComponent). It could be that you just haven't enclosed those types in a namespace, though.

        - Calling Activator.CreateInstanceFrom() will return you an ObjectHandle, which you have to call Unwrap() on prior to casting.

        - Attempting to cast to IBusinessIntelligenceComponent<>, specifying the constrained interface (i.e. IBusinessObject) in place of the actual T, will cause an InvalidCastException. Specifying a constrained T on the GetBusinessIntelligenceComponent() method, then casting using that T as the generic parameter, would be a preferable way of dealing with things.

    I've had success with your method, doing the following.

        IBusinessIntelligenceComponent<BasicBusinessObject, BasicBusinessObject, BasicCriteria> _bic = null;
        _bic = FrameworkFactories.GetBusinessIntelligenceComponent<BasicBusinessObject, BasicCriteria>();

    With the method defined like this.

         public static IBusinessIntelligenceComponent<TBusinessObject, TBusinessObject, TCriteriaObject>
                                                        GetBusinessIntelligenceComponent<TBusinessObject, TCriteriaObject>()
                where TBusinessObject: IBusinessObject
                where TCriteriaObject: ICriteriaObject

            {
                // hard-coded just for testing...
                String assemblyName = "BusinessIntelligence.dll";
                String componentTypeName = "BusinessIntelligence.BusinessIntelligenceComponent";

                String assemblyFullName = System.IO.Path.Combine(assemblyPath, assemblyName);

                return (IBusinessIntelligenceComponent<TBusinessObject, TBusinessObject, TCriteriaObject>)Activator.CreateInstanceFrom(assemblyFullName, componentTypeName).Unwrap();
            }

    • Marked as answer by DidacticDan Sunday, November 22, 2009 6:38 AM
    Friday, November 20, 2009 9:39 PM
  • Thanks for the response, Chris.
    Is the tester in the same directory as all of the assemblies (including the BusinessIntelligence assembly), or were just the two that were referenced copied to the tester's directory?

    Sorry for the ambiguity; everything about the "solution" (used very loosely) is in the same directory.

    There are issues with the code you've provided, as well, but as far as I can tell they're unrelated to this error. Eliminating those would rule out those possibilities, though.

    Yeah, the code I posted isn't the code that I actually compiled and have running. My actual code is on a machine that did not have internet access and I wasn't about to re-type all of the actual code. My intent was to convey the architectural considerations and enough detail to convey my issue, and you seem to have understood what I was conveying (thanks!).

        - The type you load with Activator.CreateInstanceFrom(), or other type loading mechanisms, must be fully qualified (i.e. BusinessIntelligence.BusinessIntelligenceComponent). It could be that you just haven't enclosed those types in a namespace, though.

    In the code that is actually executing, the type is fully qualified in a namespace and the factory method passes the fully-qualified namespace. 

        - Calling Activator.CreateInstanceFrom() will return you an ObjectHandle, which you have to call Unwrap() on prior to casting.   

    Yeah, the missing call to Unwrap() in the code I posted is the result of trying several things before posting this, including using reflection to load the assembly and associated type, and calling Activator.CreateInstance(Type) instead of Activator.CreateInstanceFrom(assemblyPath, typeName).

    - Attempting to cast to IBusinessIntelligenceComponent<>, specifying the constrained interface (i.e. IBusinessObject) in place of the actual T, will cause an InvalidCastException. Specifying a constrained T on the GetBusinessIntelligenceComponent() method, then casting using that T as the generic parameter, would be a preferable way of dealing with things.

    This is the meat of the issue. It seems that I just missed one more layer of Generics (e.g. making the factory method a generic method). Not sure why I didn't think of it, considering I implemented Generics elsewhere. I had considered implementing multiple GetBusinessIntelligenceComponent() methods with each being associated to a specific set of types; I'm sure you understand why I didn't like that scenario at all.

    I've had success with your method, doing the following.

        IBusinessIntelligenceComponent<BasicBusinessObject, BasicBusinessObject, BasicCriteria> _bic = null;
        _bic = FrameworkFactories.GetBusinessIntelligenceComponent<BasicBusinessObject, BasicCriteria>();

    With the method defined like this.

         public static IBusinessIntelligenceComponent<TBusinessObject, TBusinessObject, TCriteriaObject>
                                                        GetBusinessIntelligenceComponent<TBusinessObject, TCriteriaObject>()
                where TBusinessObject: IBusinessObject
                where TCriteriaObject: ICriteriaObject

            {
                // hard-coded just for testing...
                String assemblyName = "BusinessIntelligence.dll";
                String componentTypeName = "BusinessIntelligence.BusinessIntelligenceComponent";

                String assemblyFullName = System.IO.Path.Combine(assemblyPath, assemblyName);

                return (IBusinessIntelligenceComponent<TBusinessObject, TBusinessObject, TCriteriaObject>)Activator.CreateInstanceFrom(assemblyFullName, componentTypeName).Unwrap();
            }



    Thanks! I will certainly put this in place!

    For information, this is part of a proposed architecture and framework I am working on for my Master's thesis, and I am 52 pages into a 40 page document. Hopefully this is the only thing I missed!

    Thanks for taking the time to read this novella, understand the issue, work through my incomplete code, and provide your feedback!
    Saturday, November 21, 2009 7:39 PM