IIs<T>, IAm<T>, IHas<T>, etc? #71429
Unanswered
daiplusplus
asked this question in
Ideas
Replies: 1 comment 1 reply
-
I hope the power goes out in my district so I can get the courage to go to the park and touch some grass and look at all this codes |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
I'd like to see a type like this added to the BCL:
It could also be named
IAm<T>
orIHas<T>
, or variations thereof.TL;DR:
T
type-parameter), which could bestruct
types orclass
types, or a mix of both) to still be able to participate in object reference-equality comparisons.IIs<T>
, including structs, without boxing, while still getting the innerT
object.static operator implicit
conversion operators.IQueryable<T>
...).Motivation
I have this interface type defined in all of my personal projects because I find it essential when working with other interface-types and wrapper-types when I want to preserve object-identity - this is especially useful when working with Entity Framework types.
For example, supposing I have an EF entity class
Order
with anIReadOnlyOrder
interface added:Now if I add a separate type (either a stateful and immutable
class
- or areadonly struct
) to represent anOrder
that's loaded withCustomer
and itsLineItem
collection, then it would look like this (note how it implementsIReadOnlyOrder
and simply passes-through to its innerOrder
reference):The neat thing is that
LoadedOrder
becomes a drop-in replacement forOrder
in most places, and this application of dependent-types/refinement-types instantly solves runtime NREs in EF code that makes naïve assumptions about non-null
reference and collection navigation properties.Also, this is example is much simpler than my actual projects: instead of a single
class LoadedOrder
I'll actually have 10 or more differentLoaded...
types just to assert thatOrder
has some other combination of reference-properties and collections loaded into memory - all-in-all, there might be hundreds of theseLoaded...
types in a project.Now, for the problem that the
IIs<T>
interface type solves, consider:class LoadedOrder
is a distinct type fromclass Order
(and no, subclassingOrder
such thatclass LoadedOrder : Order
is inappropriate (if not outright incorrect) in this context) becauseOrder
is a fully mutable class, whileLoadedOrder
is an read-only view over a mutableOrder
instance.struct
instead of aclass
as the actual implementation type.Order
object can useIReadOnlyOrder
for their parameter and field types, which exposes no setters.Order
or aLoadedOrder
object as the argument for anIReadOnlyOrder
parameter, but this introduces the need for dozens of manually-maintained overloads for each type... no thanks.LoadedOrder
could also havepublic static implicit operator Order( LoadedOrder self ) => self.Order;
but this means you don't benefit from the safety benefits of theIReadOnlyOrder
interface, annoyingly there's also built-in interface to represent theimplicit operator
, which is kinda amazing considering the utility of a hypotheticalIConvertTo<T>
interface).Order
reference because it wants to pass it into theDbContext
'sDbSet<Order>
member collection:DbContext
/DbSet<T>
types are built-around using object-identity (i.e.Object.ReferenceEquals
) to identify and track in-memory objects - so if you want to work with anOrder
object with EF then you absolutely need to use theOrder
parameter type: but instead if you useIReadOnlyOrder
as the parameter-type and pass that toDbContext.Entry( readOnlyOrderArg )
then that will compile without errors but will always fail at runtime if theIReadOnlyOrder
object is not actually anOrder
object.IIs<Order>
toclass Order
(as inclass Order : IReadOnlyOrder, IIs<Order>
and implemented explicitly (to reduce member pollution) asOrder IIs<Order>.Self => this;
and in each of theLoadedOrderWith...
types aspublic Order Self => this.Order;
)TOrder
, i.e.:DoSomethingWithOrder<TOrder>( TOrder order ) where TOrder : IReadOnlyOrder, IIs<Order>
then the single methodDoSomethingWithOrder
can now accept anyIReadOnlyOrder
object that exposes its innerOrder
object (so theOrder
can be passed directly to theDbContext
) without any overloads or custom structs implementing a poor-man's intersection-type.struct
implementation ofIReadOnlyOrder, IIs<Order>
without boxing the argument value, which helps with performance at scale, while still using the exact same single generic method to handleclass
implementations too.You might suggest that instead of using
IIs<Order>
aninterface IMutableOrder
could be used instead (which is just likeIReadOnlyOrder
, except all the properties are read/write, instead of read-only), but the problem withIMutableOrder
is that any type can still implementIMutableOrder
which would break theDbContext
at runtime if implemented by any type other thanclass Order
, whereas with theTOrder : IIs<Order>
generic constraint you're making use of a compile-time guarantee that whatever object is passed as an argument you'll always be able to get the underlyingOrder
object out of it.Obviously this example is somewhat contrived - and this is a somewhat narrow-use-case too - but I've found the
interface IIs<T>
type essential in other situations too, especially when I'm using areadonly struct
as a predicate-type - for the same general reasons as above: wanting to preserve object-identity and the ability to directly get-at a underlying data objects regardless of the wrapper type (indeed, whether it's astruct
or aclass
!), and with compile-time guarantees that it won't fail at runtime.Given the sheer simplicity and utility of an
interface IIs<T>
I'm really surprised that .NET doesn't have it already...Beta Was this translation helpful? Give feedback.
All reactions