5

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...

Thomas Flinkow
  • 4,845
  • 5
  • 29
  • 65
  • 1
    You could just pass in an `object` and use `obj.GetType` instead of `typeof(TType)` – juharr Jan 18 '18 at 13:05
  • 1
    @juharr that won't give the same results though. – Jon Hanna Jan 18 '18 at 13:05
  • 1
    @JonHanna Why not? If the object is the same type as the generic type it should work. – juharr Jan 18 '18 at 13:07
  • @Rahul and juharr: still, I get the error "Using the generic method requires 2 type arguments" – Thomas Flinkow Jan 18 '18 at 13:10
  • 2
    @ThomasFlinkow I mean you'd have `public bool HasTypeAttribute(object obj)`. Basically it can not do paritial generic type inferal. It has to infer all of them or all have to be specified. – juharr Jan 18 '18 at 13:12
  • 3
    @juharr, and if the object was not the same as the generic type it would work differently. They're only going to be the same if the actual concrete type of the object (`object.GetType()`) matches the type of the expression the compiler is inferring from, which would not be the case if that type was a base type or interface. – Jon Hanna Jan 18 '18 at 13:13
  • @juharr oh, why didn't I think of that yet?! This is what I'll be using now, thanks. – Thomas Flinkow Jan 18 '18 at 13:13
  • 1
    @JonHanna Yes in that case it would be different. But I'm not sure if the OP was planning to use this to check for attributes that only exist above the objects concrete class. In that case keeping the generic type would make since as you would not need to cast the object to whatever type you wanted to check at. Which in turn would mean that it would not be infer-able. – juharr Jan 18 '18 at 13:17

2 Answers2

6

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);
Kibiz0r
  • 61
  • 2
  • 2
5

Because the C# rules don't allow that.

It would be feasible for C# to have a rule where if some of the types related to parameters (and hence could be inferred at least some of the time) and the number of explicitly given types was the same as the number of remaining un-inferrable types, then the two would work in tandem.

This would need someone to propose it, convince other people involved in the decision-making around C# it was a good idea, and then for it to be implemented. This has not happened.

Aside from the fact that features start off having to prove themselves worth the extra complexity they bring (add anything to a language and it is immediately more complex with more work, more chance of compiler bugs etc.) the question then is, is it a good idea?

On the plus, your code in this particular example would be better.

On the negative, everyone's code is now slightly more complicated, because there's more ways something that is wrong can be wrong, resulting either in code that fails at runtime rather than compile-time or less useful error messages.

That people already find some cases around inference to be confusing would point to the idea that adding another complicating case would not be helpful.

Which isn't to say that it is definitely a bad idea, just that there are pros and cons that make it a matter of opinion, rather than an obvious lack.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251