views:

85

answers:

4

Hi All,

I have a set of classes (MyClass1, MyClass2, MyClass3) that implement an interface (IMyClass).

I am interested in creating a method to fill and return a list (Collection.Generics.Lists<>) of objects that have common interface MyClassX (List<IMyClass>). But how?

Each class has a constructor MyClassX which does something different. So I can not create a BaseClass and use a common constructor for all.

By interface I can not do:

public List<IMyClass> Fill(string[] ItemsPassedByParam, IMyClass InstanceOfMyClassX)
    List MyList = new List<IMyClass>();
    foreach (item in ItemsPassedByParam)
    {
        MyList.Add (new IMyClass (item)); //can't do new from an Interface!!
    }
}

Also I tried using generics type, TMyClass in the next example, but neither has worked for me (though I have not very deep notions about generic types):

public List<TMyClass> Fill(string[] ItemsPassedByParam, Type TypeOfMyClassX)
    List MyList = new List <TMyClass> ();
    foreach (item in ItemsPassedByParam)
    {
        MyList.Add (new TMyClass (item)); //can't do new from a Generic Type!!
    }
}

I tried also to externalize the code inside the Constructor of MyClassX in a method, but I can't call the method since de object is not initialized (there is no new () ).

In these cases which is the solution?

Thanks in advance!

+2  A: 

The best option here is to create a IMyClassFactory that handles taking the parameter, figuring out which concrete type to construct, call the constructor, and return it:

public class IMyClassFactory
{
    public static IMyClass CreateInstance(string item)
    {
        switch(item)
        {
            case "SomeValue":
            case "SomeValue2":
                return new MyClass1(item);
            case "SomeOtherValue":
                return new MyClass2(item);
        }
    }
}

That factory obviously assumes that you can infer type from the string being passed in to the factory. There's a good chance that isn't the case, in which case you need to add another parameter that allows you to infer the concrete type to instantiate.

Once you have the factory, then you can make your original method use the Factory:

public List<IMyClass> Fill(string[] ItemsPassedByParam)
{
    List MyList = new List<IMyClass>();

    foreach (item in ItemsPassedByParam)
    {
        MyList.Add(IMyClassFactory.CreateInstance(item));
    }
}
Justin Niessner
IMyClassFactory? With an "I"? That naming convention will lead to confusion...
Kirk Woll
Thanks Justin!Following your way I built yours `MyClassFactory.CreateInstance()` and also made a `MyClassFactory.GetConstrucor(Type t)` to use Jared solution.
Alex
+7  A: 

It sounds like you want the Fill method to create a List<T> from the passed in string values where T is a derivation of IMyClass.

What you need to do is pass a factory function into the Fill method which allows for the creation of IMyClass derived instances.

public List<IMyClass> Fill(
  string[] ItemsPassedByParam, 
  Func<string,IMyClass> createFunc)

  List MyList = new List<IMyClass>();
  foreach (item in ItemsPassedByParam) {
    MyList.Add(createFunc(item));        
  }
}

Then at the place you call Fill you can choose which derivation of IMyClass to use

string[] items = ...;
List<IMyClass> list1 = Fill(items, item => new MyClass1(item));
List<IMyClass> list2 = Fill(items, item => new MyClass2(item));
JaredPar
Thanks Jared!I did a mix between yours and Justin Niessner's solutions. Because to remind in development time about witch is the constructor to use is not very practical for me. So I made a `MyClassFactory.CreateInstance()` and also a `MyClassFactory.GetConstrucor(Type)` to get the appropriated constructor.
Alex
+1  A: 

Maybe you can just use the factory pattern here, depending on how you judge which type is needed.

Maybe you'll need to use reflection and invoke a ConstructorInfo (passes parameters to a constructor, returns an object, good for building in template methods, but also good for blowing up in run-time when you do the equivalent of something that would be caught at compile-time).

The latter seems closest to what you've been trying, though if you can just do things from a more type-safe factory then that's probably the way to go.

public static T BuildOne(SomeParamType myParam)
{
  ConstructorInfo constrInfo = typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[]{typeof(SomeParamType)}, null);
  return (T)constrInfo.Invoke(new object[]{myParam});
}
Jon Hanna
+1  A: 

You cannot create an instance of an interface, but you can create an instance of a generic type - provided it has a default constructor. you can then pass the item to the instance to a property or method rather than using constructor injection.

public List<TMyClass> Fill(string[] ItemsPassedByParam) 
    where TMyClass : IMyClass, new() {
    ...
        IMyClass inst = new TMyClass();
        inst.SetItem(item);
        myList.Add(inst);
    ...
}

Calling new TMyClass has the same effect as calling Activator.CreateInstance<TMyClass>() The generic CreateInstance does not allow arguments to be passed to the constructor, but you can pass them with a non-genric CreateInstance.

public List<TMyClass> Fill(string[] ItemsPassedByParam) {
    ...
        myList.Add((IMyList)Activator.CreateInstance(typeof(TMyClass), item));
    ...
}
Mark H