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).
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);
}
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));
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
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...
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.
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'sc
API design was truly artful in the .NET 1.0 days
Most helpful comment
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.