Reputation: 2675
I'm trying to get a list of classes that implement an interface and then at some later point in the program, instantiate these classes and pass in parameters to their constructors.
In a previous Stack Overflow page, I saw this code that instantiated the classes with an empty constructor:
var preprocessors = from t
in Assembly.GetExecutingAssembly().GetTypes()
where t.GetInterfaces()
.Contains(typeof(Preprocessing))
&& t.GetConstructor(Type.EmptyTypes) != null
select Activator.CreateInstance(t) as Preprocessing;
But I don't want some classes to be instantiated without passing some kind of parameter to the constructor (the parameter is obtained in a for loop so I have to wait until I instantiate it).
I tried doing just this to get the list of classes to be instantiated:
var preprocessors = from t
in Assembly.GetExecutingAssembly().GetTypes()
select t.GetInterfaces()
.Contains(typeof(Preprocessing))
But after doing this, I wasn't sure how to access the classes and instantiate them. Would really appreciate some guidance on this. Thanks!!
Edit:
I can't figure out what to put in the Activator.CreateInstance(...)
parentheses. I tried putting something like this:
foreach (var sim in similarities)
{
var a = Activator.CreateInstance(sim, preprocessedData) as Preprocessing;
But that is throwing an error, most likely because preprocessedData
is a DenseMatrix
object (from the MathNet Numerics library). Is there any way to send a DenseMatrix
as parameter and not an array?
Upvotes: 3
Views: 4632
Reputation: 2675
Thank you for all the responses, they went a long way in helping me figure this out.
Some of the other solutions here may also be equivalent to what I did, but this ended up working for me.
var preprocessors = from t in Assembly.GetExecutingAssembly().GetTypes()
where t.GetInterfaces().Contains(typeof(Preprocessing))
select Activator.CreateInstance(t, originalData) as Preprocessing; // Create instance of class with originalData as parameter
// Obtain a list of all types in the assembly (will be instantiated in the foreach loop)
var similarities = Assembly.GetExecutingAssembly().GetTypes();
This is how I created an instance and passed it parameters in a for-loop:
foreach (var sim in similarities)
{
if (sim.GetInterfaces().Contains(typeof(Similarity))) // Check if the assembly types are of type Similarity
{
// Create instance of similarity class with preprocessedData as a parameter
var similarityObject = Activator.CreateInstance(sim, preprocessedData) as Similarity;
Upvotes: 0
Reputation: 2691
overload of CreateInstance with params is suitable here
public static Object CreateInstance(
Type type,
params Object[] args
)
http://msdn.microsoft.com/en-US/library/wcxyzt4d(v=vs.110).aspx
usage example
var constructorParams = new object[] { 1, "string", new object() }; //here is example of params that you will pass to each plugin constructor
var pluginTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => typeof(Preprocessing).IsAssignableFrom(t)); //all types of your plugin
var plugins = pluginTypes.Select(pluginType => Activator.CreateInstance(pluginType, constructorParams)); //instanciated plugins
UPDATE
var a = Activator.CreateInstance(sim, new object[] { preprocessedData })
Lets imagine that sim has this implementation:
class sim
{
public sim(int a, string b, AnotherType c){}
}
so to initiate this class with parametres constructor you have to pass three parametrs like:
var a = Activator.CreateInstance(sim, new object[] { 1230, "string", new AnotherType() })
as a result CLR via reflection will produce you your intanse.
Upvotes: 3
Reputation: 12435
The reflection mechanism presented by others may fail if the you have a large set of assemblies to look through - the .Net VM lazy loads, assemblies aren't loaded until they are actually referenced, that is, a class is explicitly referenced from that assembly.
If your program references 20 assemblies and has only actually loaded 2 of them, AppDomain.GetAssemblies() won't return the other 18 and you'll never bother looking through them for implementors of your interface.
There are various ways of forcing the assemblies to be loaded. You could look through your call stack, such as I do below, or you could look through your current assemblies and force-load their referenced assemblies, etc.
Below is a complete class that attempts to deal with these problems:
public class PluginMgr<T> where T : class
{
public PluginMgr()
{
this.Plugins = new List<T>();
}
/// <summary>
/// The list of plugin instances that were found and created.
/// </summary>
public List<T> Plugins { get; private set; }
/// <summary>
/// Scans loaded assemblies for classes that implement the interface specified by the type
/// parameter, then instantiates and stores any classes that are found.
/// </summary>
public void Initialize()
{
ForceLoadAssemblies();
FindPlugins();
}
/// <summary>
/// Attempts to force the VM to load all assemblies that are referenced by any assembly
/// implicated in the call stack. Referenced assemblies are not loaded until reference
/// resolution happens that depends on that assembly; if an assembly X has a reference to
/// another assembly Y, but X never uses anything from Y, then Y is never loaded; that is to
/// say, the .Net assembly loader uses 'lazy loading'.
///
/// This is necessary because our plugin sniffing logic won't find the plugins that don't
/// have their defining assembly loaded. If we forcibly load the assembly, then we'll guarentee
/// that we find the assembly.
/// </summary>
private void ForceLoadAssemblies()
{
StackFrame[] frames;
AssemblyName[] refedAssemblies;
Assembly assembly;
frames = new StackTrace().GetFrames();
foreach (StackFrame frame in frames)
{
assembly = frame.GetMethod().DeclaringType.Assembly;
refedAssemblies = assembly.GetReferencedAssemblies();
foreach (AssemblyName refedAssembly in refedAssemblies)
{
Assembly.Load(refedAssembly);
}
}
}
/// <summary>
/// Scan through every loaded assembly to find types that are implementors of our
/// given interface.
/// </summary>
private void FindPlugins()
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
Type interfaceType = typeof(T);
foreach( Assembly assembly in assemblies )
{
Type[] types = assembly.GetExportedTypes();
foreach( Type type in types )
{
if( type.GetInterfaces().Contains( interfaceType ) )
{
T plugin = Activator.CreateInstance( type ) as T;
this.Plugins.Add( plugin );
}
}
}
}
Upvotes: 0
Reputation: 74365
This should give you a list of types in the current app domain that implement the specified interface:
IEnumerable<Type> TypesImplementingInterface( Type interfaceType , params Type[] desiredConstructorSignature )
{
if ( interfaceType == null ) throw new ArgumentNullException( "interfaceType" ) ;
if ( !interfaceType.IsInterface ) throw new ArgumentOutOfRangeException( "interfaceType" ) ;
return AppDomain
.CurrentDomain
.GetAssemblies()
.SelectMany( a => a.GetTypes() )
.Where( t => t.IsAssignableFrom( interfaceType ) )
.Where( t => !t.IsInterface )
.Where( t => t.GetConstructor( desiredConstructorSignature ) != null )
;
}
Once you have that, instantiating instances of the type is easy, something along the lines of this:
T ConstructInstance<T>( Type t , params object[] parameterList )
{
Type[] signature = parameterList
.Select( p => p.GetType() )
.ToArray()
;
ConstructorInfo constructor = t.GetConstructor( signature ) ;
T instance = constructor.Invoke( parameterList ) ;
return instance ;
}
in your case, you'd want something like this:
Type[] types = TypesImplementingInterface( typeof(IFoo) , typeof(DenseMatrix) ).ToArray() ;
DenseMatrix dmInstance = ... ;
...
IFoo constructedInstance = ConstructInstance<IFoo>( types[0] , dmInstance ) ;
Upvotes: 1
Reputation: 507
The CreateInstance has a signature that takes the constructor parameters:
Activator.CreateInstance Method (Type, Object[])
EDIT
This is an example:
interface ITest
{
string Say();
}
class Test1 : ITest
{
private string message;
public Test1(string message)
{
this.message = message;
}
public string Say()
{
return this.message;
}
}
class Test2 : ITest
{
private string message;
public Test2(string message)
{
this.message = message;
}
public string Say()
{
return this.message;
}
}
void Main()
{
string[] args = new string[] { "Hello", "World" };
var tests = Assembly.GetExecutingAssembly().GetTypes()
.Where(t => t.GetInterfaces().Contains(typeof(ITest)))
.Select((t, i) => Activator.CreateInstance(t, args[i]) as ITest);
foreach (var item in tests)
{
Console.WriteLine(item.Say());
}
}
Upvotes: 0