New Posts New Posts RSS Feed: Tech Tip: Create a Generic When T is unknown
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

Tech Tip: Create a Generic When T is unknown

 Post Reply Post Reply
Author
IdeaBlade View Drop Down
Moderator Group
Moderator Group
Avatar

Joined: 30-May-2007
Location: United States
Posts: 353
Post Options Post Options   Quote IdeaBlade Quote  Post ReplyReply Direct Link To This Post Topic: Tech Tip: Create a Generic When T is unknown
    Posted: 06-Jun-2007 at 3:31pm

Level 400
DevForce Express

Sep 19, 2006

I like to write generic code that I can reuse to solve similar problems throughout my application. So it is something of a shock to discover that .NET generics can interfere with my ability to write generic code. Maybe they should have been called “specifics”.

Imagine I’ve got an idea for a general routine that works on a concrete type based on the generic collection, BindableList(Of T). I won’t know the type, ‘T’, until runtime.

There could be many reasons why I won’t know the type of ‘T’. Perhaps I’ll present the user with a ComboBox of business objects types. Once the user picks a type, I fetch every instance of this type from the database and pass the result on to my routine which pours those entities into a BindableList of that type, sorts it, and shoots it off to my UI screen builder. The screen builder reads the properties of the items in the list, lays down some UI controls, and binds those controls to the item properties. Pretty cool.

My routine will instantiate a BindableList(Of SelectedType). Of course I can’t write this directly into my code because SelectedType could be an Employee, or a Company, or who knows what.

I’m clever enough to have ensured that all my business objects inherit from a base Entity class. So I make my routine instantiate a BindableList(Of Entity), fill it with the business objects, sort it and return a result of BindableList(Of Entity). I can do that,right?

No, I can’t.

BindableList(Of Entity) is not the same type as BindableList(Of Employee), or BindableList(Of Customer). It matters not a jot that Employee inherits from Entity. My compile fails if I insert an Employee into a BindableList(Of Entity). The application will throw a runtime exception if I try to fake it out by casting through interfaces.

While I can assign an Employee instance to a variable declared as Entity, I cannot assign an object of type Generic(Of Employee) to an object of type Generic(Of Entity). Although Employee inherits from Entity, the Generic(Of Employee) class does not inherit from Generic(Of Entity).

I’m stuck.

Fortunately, .NET reflection can bail me out. I can use reflection to create a BindableList(Of T) at runtime when I know what ‘T’ is. Because BindableList(Of T) inherits from non-generic interfaces, I can proceed with my databinding example by exploiting one of those interfaces. If there is something I need that isn’t accessible through an interface, a sorting method perhaps, I can reflect again to invoke that method.

The following example guides you through the brambles and puts you on the clear path to reusable generic code.

Code Example

  C#:

 

public IBindingList CreateBindableListAtRuntime() {

 

  // Get some entities of the runtime type (e.g., Employee)

  Type entityType = typeof(Employee);

  IEnumerable entities =

    aPersistenceManager.GetEntities(entityType);

 

  // Set sort parameters

  string sortProperty = "FullName";

  ListSortDirection sortDirection = ListSortDirection.Ascending;

 

  return CreateAndSort(

    entityType, entities, sortProperty, sortDirection);

}

 

private IBindingList CreateAndSort(

  Type pTargetType, IEnumerable pObjects,

  string pSortPropertyName, ListSortDirection pSortDirection) {

 

  // (1) Create a generic BindableList of the desired type

  IBindingList result = (IBindingList)

    IdeaBlade.Util.TypeFns.ConstructGenericInstance(

      typeof(BindableList<>), new Type[] { pTargetType });

 

  // (2) Add objects to the BindableList (not type checked!)

  foreach ( object item in pObjects ) { result.Add(item); }

 

  // (3) Sort it using reflection

  SortBindableList(

    result, pSortPropertyName, pSortDirection, false);

 

  return result;

}

 

// (4) Sort BindableList using reflection

private static void SortBindableList(

    IBindingList pList, params object[] pParms) {

 

  // (5) Sort method name

  string method="ApplySort";

  

  // (6) Flags to invoke a public instance method

 BindingFlags flags =

    BindingFlags.InvokeMethod |

    BindingFlags.Public | BindingFlags.Instance;

 

  // (7) Get the concrete type of our list and

  // invoke the method via reflection

  Type listType = pList.GetType();

  listType.InvokeMember(method, flags, null, pList, pParms);

}

 

  VB.NET:

 

Public Function CreateBindableListAtRuntime() As IBindingList

 

  ' Get some entities of the runtime type (e.g., Employee)

  Dim entityType As Type = GetType(Employee)

  Dim entities As IEnumerable

  entities = aPersistenceManager.GetEntities(entityType)

 

  ' Set sort parameters

  Dim sortProperty As String = "FullName"

  Dim sortDirection As ListSortDirection

  sortDirection = ListSortDirection.Ascending

 

  Return CreateAndSort( _

    entityType, entities, sortProperty, sortDirection)

End Function

 

Private Function CreateAndSort( _

    ByVal pTargetType As Type, _

    ByVal pObjects As IEnumerable, _

    ByVal pSortPropertyName As String, _

    ByVal pSortDirection As ListSortDirection) As IBindingList

 

  ' (1) Create a generic BindableList of the desired type

  Dim temp as Object

  temp = IdeaBlade.Util.TypeFns.ConstructGenericInstance( _

    GetType(BindableList(Of ) ), _

    New Type() { pTargetType } )

  Dim result As IBindingList

  result = CType(temp, IBindingList)

 

  ' (2) Add objects to the BindableList (not type checked!)

  For Each item As Object In pObjects

    result.Add(item)

  Next item

 

  ' (3) Sort it using reflection

  SortBindableList( _

    result, pSortPropertyName, pSortDirection, False)

 

  Return result

End Function

 

' (4) Sort BindableList using reflection

Private Shared Sub SortBindableList( _

  ByVal pList As IBindingList, _

  ParamArray ByVal pParms As Object())

 

  ' (5) Sort method name

  Dim method As String="ApplySort"

 

  ' (6) Flags to invoke a public instance method

  Dim flags As BindingFlags

  flags = BindingFlags.InvokeMethod Or _

          BindingFlags.Public Or _

          BindingFlags.Instance

 

  ' (7) Get the concrete type of our list and

  ' invoke the method via reflection

  Dim listType As Type = CType(pList, Object).GetType()

  listType.InvokeMember(method, flags, Nothing, pList, pParms)

End Sub

Code Commentary

CreateBindableListAtRuntime() is simply a harness for the real action in CreateAndSort and its supporting methods.

1)Statement #1 of CreateAndSort creates an instance of the BindableList(Of T) for an arbitrary type ‘T’ which we pass in as a parameter. We’re relying on a DevForce utility method called ConstructGenericInstance; see the “End Note” for more on this.

Observe that we have to hold that instance in something known and declarable at compile time. We do that in “result” which is declared to be an IBindingList, an interface implemented by BindingList(Of T) which is the .NET base class for BindableList(Of T).

We could have chosen from a number of interfaces implemented by BindingList. We picked the interface which has the richest API, the most methods and properties.

As a general rule, in the absence of any other compelling interest, it is best to return as rich and specific an object as possible, in hopes that the caller won’t have to cast the returned result.

 2)Statement #2 pours the contents of an IEnumerable parameter into our result. In our example, the IEnumerable is an Entity array returned by a DevForce PersistenceManager.GetEntities method.

CreateAndSort doesn’t know that. It just knows it will get some kind of IEnumerable.

In general, we want our method parameters to accept the broadest possible type as long as that type is consistent with the method’s purpose.

Of course Object is the broadest possible type. It’s too broad. We know we have to enumerate over the objects to get them into the BindableList. Why make ourselves verify that it is enumerable at runtime when the compiler can do it.

CreateAndSort should confirm that all of the objects are of the same type, the specified type. It doesn’t, which means we could get a runtime exception.

We can’t delegate this job to the compiler. We don’t know the type until runtime, so we can’t specify what type of items we’ll enumerate. Nor can we use generics to enforce that all items are the same type; remember, we’re doing this exercise because we’re struggling to create just such a generic at runtime.

CreateAndSort is a private method, so the risk is low. Still, such methods have a way of becoming public. We’ll leave it to you to add the type checking.

 3)Now we want to do something with that generic BindableList. We’re going to sort it.

The easiest and most direct way would be to call the ApplySort method of IBindingList which, not coincidentally, is the declared type of our generic result variable.

Unfortunately, ApplySort takes a PropertyDescriptor for the property to sort on, and we only know the name of that property.

DevForce has some nifty ways to construct the PropertyDescriptor from the name, but that’s out of scope for this lesson.

Instead, we’ll use reflection to take advantage of our certainty that the result is actually a BindableList(Of T). A BindableList(Of T) has an ApplySort override that takes a property name.

 4)Check out the parameters of the SortBindableList method. The first holds our newly minted BindableList. Then comes pParms, an array of object, in which to pass the parameters of the ApplySort method.

Note the params keyword (ParamArray in VB). This is a sweet bit of syntactic sugar. Without it, the caller would have to construct an Object array and populate it with the parameters to ApplySort. Instead she can simply string them together at the end of the call, as in “… pSortPropertyName, pSortDirection, false)”.

These are the three parameters for the intended overload of ApplySort: the name of the property to sort on, the sort direction, and a flag that means “don’t bother to keep the list sorted.”

 5)Here, we’ve inscribed the name of the BindableList method we want to call.

 6)We build up “binding” flags to tell Reflection what we want to do. In this case, we want it to invoke a public instance method.

 7)We get the concrete type of the list. No, we can’t use the type of IBindableList. We wouldn’t bother with all of this reflection business if we could; we’d just call the method through the interface. But there is no suitable method on IBindableList, so we have to call a method on the concrete class which is some flavor of BindableList(Of T).

Finally, we tell that concrete type to reflectively invoke our method. We pass in the method name, the binding flags, a null for the Binder (we’ll let .NET use the default), the list to sort, and the parameters of our sort method.

Refactoring Opportunity

SortBindableList has the potential to be a fully general approach to invoking any method of any type. Right now it requires an IBindingList, and we’ve baked a specific method name and binding flags right into the code.

Look at it harder and see if you can remove these dependencies. We did so in the latest version of Funhouse where the method, ExtendedReflectionFns.InvokeMethod, in the IdeaBlade.Extension project would be easy to substitute for SortBindableList in this example.

End Note

The ConstructGenericInstance plays an important role in the code sample. It’s in the DevForce utility library, IdeaBlade.Util. You’ve got DevForce, right? You don’t? Well, here’s the source for this method:

  C#:

 

/// <summary>Make an instance of a generic type</summary>

/// <param name="pGenericType">

/// Type of the generic, e.g. typeof(EntityList<>)

/// </param>

/// <param name="pTypeParms">

/// TypesParameters, e.g., typeOf(Employee)

/// </param>

/// <param name="pConstructorParams">

/// Parameters to pass to the constructor

/// </param>

/// <returns>A new instance of the generic type</returns>

public static Object ConstructGenericInstance(

    Type pGenericType, Type[] pTypeParms,

    params Object[] pConstructorParams) {

  Type finalType = pGenericType.MakeGenericType(pTypeParms);

  return Activator.CreateInstance(finalType, pConstructorParams);

}

  VB.NET:

 

''' <summary>Make an instance of a generic type</summary>

''' <param name="pGenericType">

''' Type of the generic, e.g., GetType(EntityList())

''' </param>

''' <param name="pTypeParms">

''' TypesParameters, e.g., GetType(Employee)

''' </param>

''' <param name="pConstructorParams">

''' Parameters to pass to the constructor

''' </param>

''' <returns>A new instance of the generic type</returns>

Public Shared Function ConstructGenericInstance( _

    ByVal pGenericType As Type, _

    ByVal pTypeParms As Type(), _

    ParamArray ByVal pConstructorParams As Object()) _

    As Object

  Dim finalType As Type

  finalType = pGenericType.MakeGenericType(pTypeParms)

  Return Activator.CreateInstance(finalType, pConstructorParams)

End Function

 

 

Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down