The problem I am trying to solve is representing a partially loaded tree of objects using C# generics. For example, suppose we have the following classes:
public class Address
{
public string Line1 { get; set; }
public string City { get; set; }
}
public class Customer
{
public string Name { get; set; }
public Address Address { get; set; }
public ICollection Orders { get; set; }
}
public class Order
{
public int OrderNumber { get; set; }
public ICollection Lines { get; set; }
}
public class Line
{
public int Qty { get; set; }
public string Product { get; set; }
}
The classes have references to each other, but sometimes these references will not be loaded, and the fields will just be null instead. Changing these properties to be nullable causes its own issues, since the properties to always need to be checked for null, even though we know at compile time which properties are loaded and which ones aren't.
My solution is a set of wrapper types which can be automatically generated. For example, for Customer, we create the following interfaces:
public interface NoCustomer { }
public interface ICustomer : NoCustomer
where TAddr : NoAddress
where TOrders : NoOrder
{
string Name { get; set; }
}
So for example ICustomer means a customer where the address and the orders are not loaded, but ICustomer Means a customer where the address and orders are loaded, but the lines of the orders are not. We can use extension methods to make sure that at compile time you are prevented from trying to access one of these unloaded objects:
public static class CustomerExtensions
{
public static IAddress Address(this ICustomer customer)
where TOrders : NoOrder
{
...
}
}
Note that because the types are covariant, we can write functions which take a Customer and pass it a Customer just fine, or in general use a "more-loaded" object, when only a "less-loaded" object is needed.
All of this is well and good, but the problem arises when I attempt to make the types more ergonomic to use. By default, the types start to get pretty verbose and worse, you end up spending a lot of characters talking about types that aren't even loaded. For example,
ICustomer>
is annoying to write, and I would prefer to write
ICustomer>
or something similar instead. The latter is much more clear because only the names of the loaded types (Customer, and Order) appear.
However, I am having difficulty figuring out how to make this work.
- My first approach is just replacing the
NoCustomer,NoAddress, etc interfaces with a singleNointerface. The problem is that now all interface types inherit from theNotype, so you can provide wrong type arguments, and the compiler can't do anything about it (e.g.ICustomerwould compile fine!). - My second approach is to try to have both
NoCustomertypes, and also a genericNotype which inherits from all of them. So we could writeICustomer, or alternativelyICustomer, but notICustomer. This superficially seems like it will work, but the problem is thatICustomerinherits fromICustomerbut not ICustomer. If we try to add that inheritance, then we encounter the same problem, where every interface type could be used for every type parameter.
I would be interested in any creative ways to get around these limitations. I'm not specifically wedded to the idea of the exact syntax of ICustomer, I am just looking for out-of-the-box ideas on how I could improve the ergonomics and not flood your code with NoFoo, NoBar, NoBaz noise.