You can get the type to be created by calling ItemsSource.GetType().GetInterfaces()
, finding the Type
object for the IEnumerable<T>
interface (which any generic collection will implement), and calling GetGenericArguments()
on it. IEnumerable<T>
has one type argument, of course, so that's the type you need to create an instance of.
Then you can create an instance fairly easily (see UPDATE below for a static method which wraps this all up into a single method call):
ObjectType instance = (ObjectType)Activator.CreateInstance("AssemblyName",
"MyNamespace.ObjectType");
You'll need the assembly in which the type is declared, but that's a property of Type
. Assembly
has a CreateInstance
method as well. Here's another way to do the same thing:
Type otype = typeof(ObjectType);
ObjectType instance = (ObjectType)otype.Assembly.CreateInstance(otype.FullName);
If the type to be instantiated doesn't have a default constructor, this gets uglier. You'd have to write explicit code to provide values, and there's no way to guarantee that they make any sense. But at least that's a much lighter burden to impose on the consumer than a mess of IPOCOFactory
implementations.
Remember by the way that System.String
doesn't have a default constructor. It's natural to test the code below with List<String>
, but that's going to fail.
Once you have the type of the objects in ItemsSource
, you can further simplify maintenance by programmatically enumerating the names and types of the properties and auto-generating columns. If desired, you could write an Attribute
class to control which ones are displayed, provide display names, etc. etc.
UPDATE
Here's a rough implementation that's working for me to create instances of a class declared in a different assembly:
/// <summary>
/// Collection item type must have default constructor
/// </summary>
/// <param name="items"></param>
/// <returns></returns>
public static Object CreateInstanceOfCollectionItem(IEnumerable items)
{
try
{
var itemType = items.GetType()
.GetInterfaces()
.FirstOrDefault(t => t.Name == "IEnumerable`1")
?.GetGenericArguments()
.First();
// If it's not generic, we may be able to retrieve an item and get its type.
// System.Windows.Controls.DataGrid will auto-generate columns for items in
// a non-generic collection, based on the properties of the first object in
// the collection (I tried it).
if (itemType == null)
{
itemType = items.Cast<Object>().FirstOrDefault()?.GetType();
}
// If that failed, we can't do anything.
if (itemType == null)
{
return null;
}
return itemType.Assembly.CreateInstance(itemType.FullName);
}
catch (Exception ex)
{
return null;
}
}
public static TestCreate()
{
var e = Enumerable.Empty<Foo.Bar<Foo.Baz>>();
var result = CreateInstanceOfCollectionItem(e);
}
You could make CreateInstanceOfCollectionItem()
an extension method on IEnumerable
if you like:
var newItem = ItemsSource?.CreateInstanceOfCollectionItem();
NOTE
This depends on the actual collection being a generic collection, but it doesn't care about the type of your reference to the collection. ItemsControl.ItemsSource
is of the type System.Collections.IEnumerable
, because any standard generic collection supports that interface, and so can be cast to it. But calling GetType()
on that non-generic interface reference will return the actual real runtime type of the object on the other end (so to speak) of the reference:
var ienumref = (new List<String>()) as System.Collections.IEnumerable;
// fullName will be "System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"
// ...or something like it, for whatever version of .NET is on the host.
var fullName = ienumref.GetType().Name;