views:

140

answers:

4

I'm working with two third-party libraries, and racking my brain over a way to solve this particular issue. I'm implementing an interface which will pass me objects of type "object", and in a good number of the calls, I need to pass them into a generic method that expects a type with class and new() constraints defined.

I know that any object I pass through will meet these constraints, but as far as I can see, there's no simple way I can specify to a generic method that an object satifies these criteria. An interface can't specify constraints on constructors, and an abstract class isn't permitted as a type argument to the generic method.

The objects being passed in in this case are known and controlled by me, but the signature of both Delete methods can't be modified. Ideally I'd be able to just implement an interface guaranteeing the parameterless constructor criteria, but that doesn't seem to be an option.

Here's an example of what I'm talking about:

public void Delete(object toDelete) {
    _repo.Delete(toDelete)  // signature here is _repo.Delete<T>(T obj) where T : class, new()
}

To give some background and hopefully explain things - the "Delete" call is an implementation of IUpdatable in ADO.NET Data services, while the _repo.Delete call is from SubSonic. I have one DataContextProvider class that will be handling these requests (and other similar ones) for every class that I'm exposing through my model, so a direct cast to a specific class isn't feasible. I can guarantee that the classes always are classes and have a parameterless constructor, but I cannot say from the datacontext that there are only a fixed set of classes that may be passed down - ideally I'd like the DataContext to work with new classes without modification.

A: 

You can use reflection with the GetConstructors method to verify that the type has a parameterless constructor. Is that what you mean by new() constraints?

AaronLS
It's not so much a problem checking myself if the object passed in has a paramterless constructor, it's how to pass something to the library method that is expecting a "T" (where T has class and new constraints defined). There's no cast I can see that essentially says "yes, this object has a new constructor and is a class"
Ryan Brunner
You said "simple way I can check if an object satifies this criteria.". If your object already satisfies those criteria, there is no cast needed is there? Are you getting an exception or compile error?
AaronLS
+1  A: 

I suppose that this is repeating what you already know, but if you really can't alter either of those Delete methods then you're stuck.

The outer method takes a plain object as an argument and the inner method requires a T argument where T meets the class, new() constraint. Unfortunately, there's no way that those two can be reconciled without alterations.

LukeH
+2  A: 

I know that any object I pass through will meet these constraints

Its a public method. You don't have to worry about any object that you pass in. You have to worry about any object that anyone passes in.

If you know that the object meets those constraints, do you also know what type the object is? If so, then just cast it to that type.

Eric Lippert
You're right of course that it's a public method, and anything could be passed in. I guess more precisely what I should be saying is that I can personally check whether the object meets those constraints, but once I do that there's still no way to signal to the generic method that the constraints are met.
Ryan Brunner
A cast operator is a signal to the compiler that you know something the compiler does not. Can you cast the object to the type that you know it is?
Eric Lippert
+3  A: 

You need to use reflection, you need to get the MethodInfo object for the method to call on your repository, and invoke it dynamically.

Note that there could be a much simpler way to do this, but it sounds to me that you have tried this, otherwise it would be the obvious solution.

Since your "_repo" variable must be of a specific type, like Repository<Employee>, you could perhaps just cast it, like this?

_repo.Delete(toDelete as Employee);
// or
_repo.Delete((Employee)toDelete);

or, if it's generic:

_repo.Delete(toDelete as T);

If that's not an option, due to code you haven't shown, you need to resort to reflection.

Here's the example:

using System;
using System.Reflection;

namespace ConsoleApplication14
{
    public class Program
    {
        static void Main(string[] args)
        {
            Dummy d = new Dummy();
            EntityType e = new EntityType();
            d.Delete(e);

            Console.In.ReadLine();
        }
    }

    public class EntityType
    {
        public EntityType()
        {
        }
    }

    public class Dummy
    {
        private Repository<EntityType> _repo = new Repository<EntityType>();

        public void Delete(object toDelete)
        {
            Type t = _repo.GetType();
            Type genericType = t.GetGenericArguments()[0];
            MethodInfo mi = t.GetMethod("Delete",
                BindingFlags.Public | BindingFlags.Instance,
                null, new Type[] { genericType }, new ParameterModifier[0]);

            // _repo.Delete(toDelete);
            mi.Invoke(_repo, new Object[] { toDelete });
        }
    }

    public class Repository<T>
        where T: class, new()
    {
        public void Delete(T value)
        {
            Console.Out.WriteLine("deleted: " + value);
        }
    }
}
Lasse V. Karlsen
Hmm, if you already know the generic type of the repository (as in this example), couldn't you simplify things by just casting the object and calling _repo.Delete?
Jeff Sternal
Yeah, I edited the answer while you typed that, I suspect he doesn't know the type of the repository either, otherwise it would be a rather obvious solution.
Lasse V. Karlsen
No, I don't know the type of the parameter in the repository (I do know that it will be one of several types, but nothing beyond that) I suppose I could switch based on the type name and call delete for each class, but that seems a little ugly. Then again, it seems like there's not a "non-ugly" answer here.
Ryan Brunner
Also, the class of _repo itself has no generics, it's specifically the method that has a generic parameter.
Ryan Brunner