Force T as reference type in code [CS0452 Error]

I have the following Problem with the T and the option for generic methods to restrict it to be e.g. a reference type (where T : class). So I got the following code:

public void mySave<T>(string filename, T obj)
{
    Session s = connect(filename);
    s.Save(obj);
}

with

public Document Save<TEntity>(TEntity entity) where TEntity : class;

there is no way to declare mySave where T : class because I get it from an interface. Anyway I need to solve CS0452 (The type 'type name' must be a reference type in order to use it as parameter 'parameter name' in the generic type or method 'identifier of generic')

Any Ideas on doing that?


Solution 1:

If some generic parameter has some constraints, any other generic parameter used as argument must, at least, provide same type guarantees.

Since generics are a compile-time feauture, just imagine that Save<TEntity> requires TEntity to be a reference type, and you could give an argument that may or may not be a value type. This would defeat the purpose of generics.

Your best bet is adding the whole generic constraint (i.e. T : class) to the interface too.

Solution 2:

Sriram suggested two ways... The more correct way (explored by Matías Fidemraizer) is to change the interface. The other way is through reflection. I support fully the way explored by Matías Fidemraizer, and think that it is the "right" way. But sometimes you can't do what is right, and you have to do what works.

This is the other way, through reflection. Note that this code goes further than necessary, caching the delegate so that further calls with the same T type won't need to reuse reflection and will be faster.

public static class SessionHelper
{
    public static void Save2<T>(this Session session, T obj)
    {
        SessionHelperImpl<T>.Save(session, obj);
    }

    private static class SessionHelperImpl<T>
    {
        public static readonly Action<Session, T> Save;

        static SessionHelperImpl() 
        {
            MethodInfo saveT = (from x in typeof(Session).GetMethods()
                                where x.Name == "Save" && x.IsGenericMethod
                                let genericArguments = x.GetGenericArguments()
                                where genericArguments.Length == 1
                                let parameters = x.GetParameters()
                                where parameters.Length == 1 && parameters[0].ParameterType == genericArguments[0]
                                select x).Single();

            MethodInfo save = saveT.MakeGenericMethod(typeof(T));

            Save = (Action<Session, T>)save.CreateDelegate(typeof(Action<Session, T>));
        }
    }
}

Use it like:

s.Save2(obj);

Note that if you try to pass a non-reference type as obj you will get a TypeInitializationException. You can't really bypass the where T : class of the Save. You can only bypass it during compilation time and delay it to runtime.