Runtime: API Proposal: Type.IsAssignableTo(Type)

Created on 29 Aug 2019  路  18Comments  路  Source: dotnet/runtime

Background

The api Type.IsAssignableFrom(Type) is quite common to mistakenly use backwards because it flows unexpectedly with surrounding code (swapping the subject and object; the type you are interested in becoming a parameter rather than staying as the caller).

Proposal

To alleviate this introducing a reversal of the parameters either as extension or method would be helpful to differentiate and provide clarity:

partial class Type
{
    public bool Type.IsAssignableTo(Type type) 
        => type.IsAssignableFrom(this);

    public static bool IsAssignableTo<T>()
        => typeof(T).IsAssignableFrom(this);
}

or

partial static class TypeExtensions
{
    public static bool Type.IsAssignableTo(this Type type, Type assignableType)
        => assignableType.IsAssignableFrom(type);

    public static bool IsAssignableTo<T>(this Type type)
        => typeof(T).IsAssignableFrom(type);
}

Precedence

Type.IsSubclassOf(Type) works in this direction so currently the syntax is

typeof(derived).IsSubclassOf(typeof(base));

typeof(base).IsAssignableFrom(typeof(derived)); // swapped subject object

typeof(IInterface).IsAssignableFrom(typeof(Implementation)); // swapped subject object

While reversing

typeof(derived).IsSubclassOf(typeof(base)); 

typeof(derived).IsAssignableTo(typeof(base)); 

typeof(Implementation).IsAssignableTo(typeof(IInterface)); 

Example usage

Given

public class ConstrainedGeneric<T> where T : Stream
{}

Rather than writing

public static void Main()
{
    Type genericT = typeof(ConstrainedGeneric<>);
    Type genericParam = genericT.GetGenericArguments()[0];

    if (typeof(Stream).IsAssignableFrom(genericParam))
    {
        Console.WriteLine(true);
    } 

    // Displays True.
}

You can write

public static void Main()
{
    Type genericT = typeof(ConstrainedGeneric<>);
    Type genericParam = genericT.GetGenericArguments()[0];

    if (genericParam.IsAssignableTo(typeof(Stream)))
    {
        Console.WriteLine(true);
    } 

    // Displays True.
}

/cc @NickCraver @davkean @davidfowl

api-approved area-System.Reflection

Most helpful comment

don't think they convey the correct relationship with an interface?

I think this is part of what makes IsAssignableFrom quirky for me. The method name attempts to be so precise in what it does ("assignable") that it causes me to stop and think about it every time and has lost meaning. This API name is "technically correct, the best kind of correct" but harder for me to immediately understand the relationship between the call site and the parameter.

All 18 comments

Depending on what the goal/intent of this method is, I really liked @NickCraver's method name suggestion IsBasedOn(), or maybe IsSubtypeOf() to go along with IsSubclassOf()?

Or, alternatively...
why not both meme

While "based on" and/or "subtype" work with a class hierarchy; don't think they convey the correct relationship with an interface?

don't think they convey the correct relationship with an interface?

I think this is part of what makes IsAssignableFrom quirky for me. The method name attempts to be so precise in what it does ("assignable") that it causes me to stop and think about it every time and has lost meaning. This API name is "technically correct, the best kind of correct" but harder for me to immediately understand the relationship between the call site and the parameter.

The method name attempts to be so precise in what it does ("assignable") that it causes me to stop and think about it every time and has lost meaning.

Its also incorrect as it doesn't take into account explicit and implicit casting which are technically assignable (in C#)

"IsAssignableFrom" is an api that I always get wrong on the first try. I'm not sure if "IsAssignableTo" would work better for me, but I like "IsSubtypeOf", I think that would be intuitive to use.

I think part of getting this right is figuring out what questions devs are trying to ask: e.g.

  • Can I use X as a plugin for type Y?
  • Can I safely cast X to Y?
  • Does X implement Y?

While the answers may be the same for all of these, the questions are subtly different.

What are some other questions we're currently currently using IsAssignableFrom() to answer?

I like "IsSubtypeOf", I think that would be intuitive to use.

I would be hesitant to do that considering there's already an IsSubclassOf with slightly different semantics (it ignores interfaces).

Other ideas for names / extensions I've created in the past: Implements(otherType), InheritsFrom(otherType) -- again, I think it depends on the user's goals and how directly the framework should support those :thinking:

From that Twitter thread:

it's the usb stick of the dotnet world.

True :rofl:


But is IsAssignableTo actually clearer? I think it has the same source of confusion. And now you additionally need to pick between the "from" and "to" APIs.

I think the problem is not the from/to direction. The problem is that "assignable" is not intuitively understood. If this usability issue is to be fixed it should be by changing the verb, not the to/from suffix. When doing that we can also change the direction if that seems desirable. But that would be a secondary thing.

I could not agree more with "IsAssignableTo" or "IsClassOrSubclassOf" as it flows with the rest of my logic more normally.

Its also incorrect as it doesn't take into account explicit and implicit casting

Indeed. How such a misleading name with such a broken implementation got to be placed in public API's is baffling.

I've been banging my head against this for the last few days. I have two types - one data struct and one (generic) XML serializer. IsAssignableFrom is, based on the name, _completely broken_. It responds false for both ways of assigning between the two, yet when actually assigning between the two types it's not only compilable but also works as expected. All this pain due to the bad naming and/or inadequate implementation not accounting for implicit conversion operators or even (conversion) contructors.

Since this method is evidently not about assignability, merely the limited subset of assignability that comes with derivation, the method shouldn't even have "Assignable" in the name.

@tamlin-mike I think your position is that either the reflection APIs should implement C# semantics cleanly or they should not suggest that they do.

I think the APIs are indeed a weird hybrid between C#/VB.NET and CLR semantics. The whole Binder infrastructure, for example, is a pretty weird design mistake.

It would have been the cleanest solution if the reflection APIs were implementing purely CLR semantics and giving raw access to data. The C# semantics should have been layered on top of that with another optional library.

Now, we are stuck with this forever for compatibility.

I believe that there are plans to add support for nullability annotations to the reflection APIs. That seems to just perpetuate this design issue. Nullability annotations are not a CLR concept. They are specific to certain languages.

the APIs are indeed a weird hybrid between C#/VB.NET and CLR semantics. The whole Binder infrastructure, for example, is a pretty weird design mistake.

Yes, the Binder infrastructure is a weird hybrid.

Type.IsAssignableFrom that this issue is about implements verifier-assignable-to CLR semantics as defined in ECMA-335. I agree that the name could have been better, but the behavior is not a hybrid.

but the behavior is not a hybrid

That is interesting to learn and makes much sense.

Approved with a parameter name that better helps with usage.

C# public partial class Type { public bool IsAssignableTo(Type? targetType) => targetType == null ? false : targetType.IsAssignableFrom(this); }

We also discussed renaming the parameter for IsAssignableFrom, but we would tackle that as a separate proposal.

FINALLY!

FWIW, as @reflectronic pointed out

For whoever mentioned this in API review, the parameter name for IsAssignableFrom is not type, it's c
API design was truly artful in the .NET 1.0 days

Was this page helpful?
0 / 5 - 0 ratings

Related issues

matty-hall picture matty-hall  路  3Comments

noahfalk picture noahfalk  路  3Comments

omariom picture omariom  路  3Comments

jkotas picture jkotas  路  3Comments

btecu picture btecu  路  3Comments