views:

820

answers:

4

I need to create an instance of an object and the type of that object will be determined at run time. The type of the object is pulled from SQL and set to a string value. I also need to pass a number of parameters when instantiating it. The number/type of parameters will be the same every time (for now at least). What do I need to use to accomplish this, Activator.CreateInstance? Any help would be appreciated.

    private void StartScans(int scan_typeid, SqlDataReader drActiveServers)
    {
        string sql = "SELECT scan_typeclass from scan_types WHERE scan_typeid = " + scan_typeid.ToString();
        sqlconn.Open();
        SqlCommand cmd = new SqlCommand(sql, sqlconn);
        SqlDataReader drScanClass = cmd.ExecuteReader(CommandBehavior.CloseConnection);

        string scan_class = drScanClass["scan_typeclass"].ToString();

        //Create object here

    }

EDIT:

Richard Berg's solution worked in a console app but not in the above example, I've dumped scan_class and verified its getting a value however I keep getting this error:

System.ArgumentNullException: Value cannot be null. Parameter name: type

Here's what my updated code looks like:

        try
        {
            string sql = "SELECT scan_typeclass from scan_types WHERE scan_typeid = " + scan_typeid.ToString();
            sqlconn3.Open();
            SqlCommand cmd = new SqlCommand(sql, sqlconn3);
            SqlDataReader drScanClass = cmd.ExecuteReader();
            drScanClass.Read();

            string scan_class = drScanClass["scan_typeclass"].ToString();

            var type = Type.GetType(scan_class);
            var myObj = Activator.CreateInstance(type, scan_id, scan_name, interval, drActiveServers);

        }
        catch (Exception e)
        {
            string sSource = "SharedAuditSVC";
            string sLog = "Application";
            string sEvent = e.ToString();

            if (!EventLog.SourceExists(sSource))
                EventLog.CreateEventSource(sSource, sLog);

            EventLog.WriteEntry(sSource, sEvent);
            EventLog.WriteEntry(sSource, sEvent, EventLogEntryType.Warning, 0);

        }
+7  A: 

Yes, exactly.

var type = Type.GetType(scan_class);
var myObject = Activator.CreateInstance(type, constructorArg1, constructorArg2, [...] );

// use myObject - you'll have to reflect on any properties that aren't derived from System.Object

EDIT

If the constructor is static or non-public, or the parameters you're passing create ambiguity in the constructors' overload resolution, then you'll need to use MethodInfo.Invoke() instead of Activator.CreateInstance().

var scan_class = "WindowsServiceAudit";
var bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
var constructorArgs = new object[] { scan_id, scan_name, interval, drActiveServers };
var constructorTypes = from p in constructorArgs select p.GetType();

var type = Type.GetType(scan_class);            
var method = type.GetMethod(scan_class, bindingFlags, System.Type.DefaultBinder, constructorTypes.ToArray(), null);
var myObject = method.Invoke(null, bindingFlags, System.Type.DefaultBinder, constructorArgs, CultureInfo.CurrentCulture);

Also make sure that:

  1. The type name is fully qualified. Reflection doesn't know anything about "using" statements.
  2. The assembly where the type resides is loaded. Use Assembly.Load() if necessary.
Richard Berg
A: 

Meh, I think it's scope related though I have had no success calling my custom class through this method. I'll sleep on it.. :)

Works:

WindowsServiceAudit WSA = new WindowsServiceAudit(scan_id, scan_name, interval, drActiveServers);

Doesn't work:

string scan_class = "WindowsServiceAudit";               

var type = Type.GetType(scan_class);
var myObj = Activator.CreateInstance(type, scan_id, scan_name, interval, drActiveServers);
jw0rd
If the constructor is static or non-public, or the parameters you're passing create ambiguity in the constructors' overload resolution, then you'll need to use MethodInfo.Invoke() instead of Activator. I'll edit my answer with another example.
Richard Berg
A: 

The constructor is not static and is public. I tried Richard's second suggestion with no luck, another null object error.

System.NullReferenceException: Object reference not set to an instance of an object. at SharedAuditSVC.Scan.StartScans(Int32 scan_id, String scan_name, Int32 scan_typeid, Int32 interval, SqlDataReader drActiveServers) in C:\shared_audit\SVC\SharedAuditSVC\SharedAuditSVC\Scan.cs:line 84

line 84 is:

var method = type.GetMethod(scan_class, bindingFlags, System.Type.DefaultBinder, constructorTypes.ToArray(), null);

I'm obviously doing something horribly wrong as it's not getting type of "WindowsServiceAudit". The project is a Windows Service, my question involves 2 of the .cs source files, Scan.cs and WindowsServiceAudit.cs. Scan.cs is where all my code snippets are being pulled from, WindowsServiceAudit is the class I'm trying to instantiate.

namespace SharedAuditSVC
{
    class Scan
    {

namespace SharedAuditSVC
{
    public class WindowsServiceAudit
    {

Both are part of the same namespace so it really has me confused. I also tried referencing it as SharedAuditSVC.WindowsServiceAudit

Again to be clear I can create it just fine by doing the following:

WindowsServiceAudit WSA = new WindowsServiceAudit(scan_id, scan_name, interval, drActiveServers);
jw0rd
Richard Berg
I think I'm getting somewhere thanks to your help. I had to put the namespace in front of the class I was trying to instantiate.var scan_class = "SharedAuditSVC.WindowsServiceAudit";That was one of my problems. Now I'm just trying to implement your solution with type.GetConstructors() instead of type.GetMethod()
jw0rd
Yup, prepending the namespaces is what I meant by "fully qualifying" the type names.
Richard Berg
A: 

Finally got it right :)

string sql = "SELECT scan_typeclass from scan_types WHERE scan_typeid = " + scan_typeid.ToString();
sqlconn3.Open();
SqlCommand cmd = new SqlCommand(sql, sqlconn3);
SqlDataReader drScanClass = cmd.ExecuteReader(CommandBehavior.CloseConnection);
drScanClass.Read();

var scan_class = "SharedAuditSVC." + drScanClass["scan_typeclass"].ToString();

Type[] argTypes = new Type[] { typeof(System.Int32), typeof(System.String), typeof(System.Int32), typeof(System.Data.SqlClient.SqlDataReader) };
object[] argVals = new object[] { scan_id, scan_name, scan_typeid, drActiveServers };
var type = Type.GetType(scan_class);
ConstructorInfo cInfo = type.GetConstructor(argTypes);
var myObject = cInfo.Invoke(argVals);

Now I can even make the args dynamic, eventually. Thanks for the help!

jw0rd
Glad it worked! Isn't reflection fun? (eek!)
Richard Berg