Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Infer generic type with two generic type parameters [duplicate]

I have the following method

public bool HasTypeAttribute<TAttribute, TType>(TType obj)
{
    return typeof(TType).GetCustomAttribute<TAttribute>() != null;
}

and I want to be able to use it like this:

MyClass instance = new MyClass();

TypeHelper.HasTypeAttribute<SerializableAttribute>(instance);

but I can't get it working because of the

incorrect number of type parameters

so that I need to call

TypeHelper.HasTypeAttribute<SerializableAttribute, MyClass>(instance);

which certainly makes sense, but why can the compiler not infer the type of the passed object? Because if the method looked like this:

public void Demo<T>(T obj)
{
}

the compiler would certainly be able to infer the type, so that I can write

Foo.Demo(new Bar());

instead of

Foo.Demo<Bar>(new Bar());

So, is there a way to make type inference work in this case? Is it a design flaw by me or can I achieve what I want? Reordering the parameters doesn't help too...

like image 737
Thomas Flinkow Avatar asked Jan 18 '18 13:01

Thomas Flinkow


1 Answers

You could break the call into multiple steps, which lets type inference kick in wherever it can.

public class TypeHelperFor<TType>
{
    public bool HasTypeAttribute<TAttribute>() where TAttribute : Attribute
    {
        return typeof(TType).GetCustomAttribute<TAttribute>() != null;
    }
}

public static class TypeHelper
{
    public static TypeHelperFor<T> For<T>(this T obj)
    {
        return new TypeHelperFor<T>();
    }
}

// The ideal, but unsupported
TypeHelper.HasTypeAttribute<SerializableAttribute>(instance);
// Chained
TypeHelper.For(instance).HasTypeAttribute<SerializableAttribute>();
// Straight-forward/non-chained
TypeHelper.HasTypeAttribute<SerializableAttribute, MyClass>(instance);

That should work alright for this case, but I'd warn against using it in cases where the final method returns void, because it's too easy to leave the chain half-formed if you're not doing anything with the return value.

e.g.

// If I forget to complete the chain here...
if (TypeHelper.For(instance)) // Compiler error

// But if I forget the last call on a chain focused on side-effects, like this one:
// DbHelper.For(table).Execute<MyDbOperationType>();
DbHelper.For(table); // Blissfully compiles but does nothing

// Whereas the non-chained version would protect me
DbHelper.Execute<MyTableType, MyDbOperationType>(table);
like image 149
Kibiz0r Avatar answered Sep 28 '22 16:09

Kibiz0r