I have no multiple inheritance and I must scream



  • Ok, C# as usual.

    I'm building a bunch of POCO (plain ol' C# objects) classes to serve as an API interface to JavaScript clients. A lot of these POCOs contain the same sets of fields, and so I've been using simple inheritance to implement that.

    For example, every request that requires authentication inherits from AuthenticatedAPIRequest.cs (which includes an optional Token field consumers can use to give authentication information in environments where cookies aren't sufficient, such as desktop applications.)

    So far so good.

    But here's a problem: all of my reports use similar information to specify the date range of the report and the interval between data points. Like this:

    public class APIReportRequest
    {
      public DateTimeOffset StartDate {get; set;}
      public DateTimeOffset EndDate {get; set;}
      public APIReportInterval Interval {get; set;}
    }
    

    Now here's the problem: some of the API report endpoints require authentication and some don't. For those that do, I somehow need to inherit both from AuthenticatedAPIRequest and APIReportRequest. And C# has no way of doing that, because it doesn't have multiple inheritance. So here's the options:

    1. Composition over inheritance, like the motto goes. So instead of having the report's data "merge" with the date/time/interval above, the report request would just include an APIReportRequest object in it. (And I'd rename the object to something more sensible). But this makes the front-end code needlessly complex, as it has to deal with a sub-object that is useless and nobody wants.

    2. Create two versions of APIReportRequest, one of which is authenticated and one of which isn't. This is my current front-runner idea, but generally duplicating code is a major WTF. (In this case it's less of a WTF because the duplication is just the models and not the actual methods that operate on them.)

    3. Since AuthenticatedAPIRequest only contains two fields, copy them into every report request manually. This breaks my current authentication code, which expects to be passed an object that inherits from AuthenticatedAPIRequest, but could be made to work with an override or something. Also: more code duplication.

    Any suggestions?



  • @blakeyrat Interestingly someone on StackOverflow recommended doing this using the Dynamic type, which actually could be made to work, but... you're also just shitting strong typing down the drain.


  • BINNED

    @blakeyrat For option 1 I think you can provide multiple interface inheritance and use composition internally, so the user of the API can deal with it as if it had MI.
    (I didn't give this much thought, don't yell if I got it wrong).



  • @topspin I'm not sure I'm visualizing what you're suggesting.

    So instead of a class, you make APIReportRequest an interface, ok...

    But then the actual POCO still has to duplicate all of the properties, you still have to type them out, and you still break everything if you add or remove a property to the interface. (The silver lining is at least the compiler would bitch, so it wouldn't be a silent breakage.)

    It seems worse than the options 1 & 2 I'm looking at.



  • @blakeyrat You know, thinking about this a bit more, I'm going to talk to some stakeholders and see if we can just make everything authenticated. Or, if absolutely needed, give non-authenticated users an API endpoint where they can obtain a temporary token for this purpose (which also means making a new access level... hm.)

    It still sucks to have to change our design due to language issues.


  • Banned

    Are you allowed to use T4 or some other code generator? Because it's a perfect use case for code generator. If I understood correctly, you don't care about type hierarchy and just don't want to repeat fields? It should be easy enough to write down those structs as list of types and list of additional fields. Then have the generator recursively traverse specifications of supertypes to produce all field definitions. And if you do care about hierarchy - it can be done too in pretty much the same way, it's just the generator function gets a bit harder to write since it has to produce interface definitions too.



  • Sort of building on topspin's idea, I'd recommend making APIReportRequest and AuthenticatedAPIRequest interfaces. Then create a couple of abstract classes that implement the various combinations of those: AbstractAPIReportRequest, AbstractAuthenticatedAPIRequest, AbstractAuthenticatedAPIReportRequest, etc., Then inherit the appropriate one in your POCOs.

    You'll probably end up with some code duplication in the abstract classes, which sucks. This would also be annoying to scale up to lots of interfaces, but should get you the granularity you need for your request objects.



  • @gąska said in I have no multiple inheritance and I must scream:

    Are you allowed to use T4 or some other code generator?

    I'm "allowed" to do whatever I want, but my tendency is to KISS and adding T4 code generation is not that. :)

    @anguirel said in I have no multiple inheritance and I must scream:

    Sort of building on topspin's idea, I'd recommend making APIReportRequest and AuthenticatedAPIRequest interfaces. Then create a couple of abstract classes that implement the various combinations of those: AbstractAPIReportRequest, AbstractAuthenticatedAPIRequest, AbstractAuthenticatedAPIReportRequest, etc., Then inherit the appropriate one in your POCOs.

    I saw that; it's apparently called the "Twin Pattern", where you make a base class/interface with the required fields then create two "twins" which only contain the differences. It's basically the same as my option 2, but using an interface instead of a class.

    In any case, thinking about the problem more, I realized I have to use composition. Because one of the requirements is that the API echo-back the parameters used to generate the report, and if the parameters inherited from AuthenticatedAPIRequest it'd also echo back pointless empty properties that the front end doesn't want nor need. So option 1 it is.



  • @blakeyrat said in I have no multiple inheritance and I must scream:

    So option 1 it is.

    Agreed. Option 1 is the best.



  • @cartman82 said in I have no multiple inheritance and I must scream:

    Agreed. Option 1 is the best.

    Yeah but they all kind of suck. I never in a million years thought I'd have a reason to miss C++.



  • @blakeyrat said in I have no multiple inheritance and I must scream:

    Yeah but they all kind of suck. I never in a million years thought I'd have a reason to miss C++.

    Here comes Ben Lubar to tell us how go supports multiple inheritence.


  • kills Dumbledore

    Do you ever need anything to have the authentication but not the rest of the api request fields? If not, why not just have the authenticated request inherit from the base request?

    I'm assuming I'm missing something here otherwise that's something that seems really obvious that everyone else has missed



  • @jaloopa said in I have no multiple inheritance and I must scream:

    Do you ever need anything to have the authentication but not the rest of the api request fields?

    Yeah, all the API requests that aren't reports. (To give one obvious example: the list of reports a user has permissions to see.)



  • @blakeyrat Composition is your best option. Make a class or two for the shared fields, and have your APIRequest classes/objects have a field pointing at a shared fields type of object.



  • @captain Right; but it's still a shitty solution because I shouldn't have to dump my problems onto the front-end to deal with.

    Especially since JavaScript has prototype-based inheritance and would have absolutely no problem with doing this in a sensible way.



  • @blakeyrat said in I have no multiple inheritance and I must scream:

    @captain Right; but it's still a shitty solution because I shouldn't have to dump my problems onto the front-end to deal with.
    Especially since JavaScript has prototype-based inheritance and would have absolutely no problem with doing this in a sensible way.

    If you really REALLY want to avoid exposing nested objects to clients, you can fix the problem on the JSON stringifier/parser level. Add annotations that override default behavior and force the props to be loaded from/to a flat JS object.


  • Impossible Mission - B

    @blakeyrat said in I have no multiple inheritance and I must scream:

    @topspin I'm not sure I'm visualizing what you're suggesting.

    So instead of a class, you make APIReportRequest an interface, ok...

    But then the actual POCO still has to duplicate all of the properties, you still have to type them out, and you still break everything if you add or remove a property to the interface. (The silver lining is at least the compiler would bitch, so it wouldn't be a silent breakage.)

    It seems worse than the options 1 & 2 I'm looking at.

    This is one of the things from Delphi I really miss in the .NET world. It has a very simple solution for this specific problem: interface delegation. It basically means that you can do something like this:

    class Foo: IBar {
       int _field1;
       string _field2;
       BarImplementation _barHandler handles IBar;
    }
    

    And then all of the calls to the IBar methods on Foo get automagically routed to the IBar implementation on _barHandler.

    It would be really nice if CLR code could do that...


  • Considered Harmful

    @masonwheeler Kotlin does that too.

    class Foo(val field1: Int, val field2: String, val barHandler: BarImplementation): Bar by barHandler


  • If you want to avoid the duplication, you could possibly write an abstract class that just implements the composition with forwarding properties... but since these are all essentially autoproperties that seems like just as much of a waste as making a second base class without the composition.

    This is actually particularly difficult because the scenario is fairly simple.


  • Trolleybus Mechanic

    @blakeyrat said in I have no multiple inheritance and I must scream:

    @cartman82 said in I have no multiple inheritance and I must scream:

    Agreed. Option 1 is the best.

    Yeah but they all kind of suck. I never in a million years thought I'd have a reason to miss C++.

    I understand the main reasoning about why it's disallowed in newer languages. I'm kind of surprised it was strictly disallowed instead of just putting some guards into it. If you inherit from two disparate classes that isn't a problem. If you inherit from two classes that share a public field or function, that's where all the problems I'm aware of arise.

    But to be fair, it would make compilers more complicated to enforce that. I don't know enough about compilers to know if it'd be enough to matter.



  • @mikehurley Yeah but imagine all the compiler contortions they put in to make yield return x; work. It's not like they were lazy when making the compiler.


  • Notification Spam Recipient

    I had this kind of problem before. I went with solution 2.

    When choosing a solution I always ask myself if using it is worth it. I mean, everything has its downsides.
    If I need to create additional interfaces, abstract classes, etc, and write 50 lines of new code, to avoid repeating 5 lines... does it make sense? I often find that simplicity is just better.


  • Trolleybus Mechanic

    @blakeyrat said in I have no multiple inheritance and I must scream:

    @mikehurley Yeah but imagine all the compiler contortions they put in to make yield return x; work. It's not like they were lazy when making the compiler.

    Like I said, I'm no compiler expert. However I'd expect the compiler code that just creates a bit of extra code to be a lot simpler than something that needs to hook in properly with the CLR type system (vtables or whatever else is relevant).

    It could also be it's less of a priority.

    I wonder if you could use something like Castle's dynamic proxy to Frankenstein up something.


  • Impossible Mission - B

    @mrl said in I have no multiple inheritance and I must scream:

    If I need to create additional interfaces, abstract classes, etc, and write 50 lines of new code, to avoid repeating 5 lines... does it make sense? I often find that simplicity is just better.

    Ugh, this, so much this. I wish my team lead on the project I'm currently working on understood this! I say YAGNI and it goes in one ear and out the other. No, let's instead pile layers of architecture atop layers of architecture!



  • @mikehurley said in I have no multiple inheritance and I must scream:

    I wonder if you could use something like Castle's dynamic proxy to Frankenstein up something.

    KISS KISS KISS KISS KISS.

    You guys can over-complicate your own projects as much as you want.



  • @mikehurley said in I have no multiple inheritance and I must scream:

    I'm kind of surprised it was strictly disallowed instead of just putting some guards into it.

    I'm glad they did it. Multiple-inheritance is a WTF full of edge cases and non-intuitive stuff


  • Trolleybus Mechanic

    @blakeyrat said in I have no multiple inheritance and I must scream:

    @mikehurley said in I have no multiple inheritance and I must scream:

    I wonder if you could use something like Castle's dynamic proxy to Frankenstein up something.

    KISS KISS KISS KISS KISS.

    You guys can over-complicate your own projects as much as you want.

    KISS would be to just duplicate your fields "public TYPE NAME {get;set;} " isn't really "code". At worst it's a mild pain when new fields need to be added to both places.


  • Trolleybus Mechanic

    @sockpuppet7 said in I have no multiple inheritance and I must scream:

    @mikehurley said in I have no multiple inheritance and I must scream:

    I'm kind of surprised it was strictly disallowed instead of just putting some guards into it.

    I'm glad they did it. Multiple-inheritance is a WTF full of edge cases and non-intuitive stuff

    What edge cases exist if the definitions of the involved classes are 100% disparate?



  • @mikehurley The problem of worrying about your classes keeping disparate, and having a stupid feature that only works on this case?

    That is ridiculous, and interfaces and composition solve all the problems that multi-inheritance solved in a much less WTF way.



  • @mikehurley Also, the order your constructors and destructors are called can be confusing.


  • Trolleybus Mechanic

    @sockpuppet7 said in I have no multiple inheritance and I must scream:

    @mikehurley The problem of worrying about your classes keeping disparate, and having a stupid feature that only works on this case?

    That is ridiculous, and interfaces and composition solve all the problems that multi-inheritance solved in a much less WTF way.

    If you're talking about objects with "real" code in them this all makes sense. But in Blakey's case where his objects are just field buckets, neither of those options are very good.


  • Banned

    @blakeyrat said in I have no multiple inheritance and I must scream:

    @mikehurley Yeah but imagine all the compiler contortions they put in to make yield return x; work. It's not like they were lazy when making the compiler.

    They didn't make multiple inheritance not because it's hard, but because it's a very bad idea.



  • @mrl said in I have no multiple inheritance and I must scream:

    If I need to create additional interfaces, abstract classes, etc, and write 50 lines of new code, to avoid repeating 5 lines... does it make sense? I often find that simplicity is just better.

    Depends on whether those 5 lines of code need to match, and whether this is a major single-point-of-failure.



  • @gąska said in I have no multiple inheritance and I must scream:

    @blakeyrat said in I have no multiple inheritance and I must scream:

    @mikehurley Yeah but imagine all the compiler contortions they put in to make yield return x; work. It's not like they were lazy when making the compiler.

    They didn't make multiple inheritance not because it's hard, but because it's a very bad idea.

    Understood.

    But how hard would it be for them to cook up something like a formal composite that automates this.

    a
    {
    public composite b;
    protected composite c;
    protected composite d;
    }
    
    b
    {
        public e;
        protected f;
        private g;
    }
    
    c
    {
        public h;
        protected i;
        private j;
    }
    
    d
    {
        public k;
        protected l;
        protected m;
    }
    

    a.e is public
    a.f is protected
    a.g is private
    a.h is protected
    a cannot access i
    a cannot access j
    a.k is private
    a cannot access l
    a cannot access m



  • @xaade said in I have no multiple inheritance and I must scream:

    Understood.
    But how hard would it be for them to cook up something like a formal composite that automates this.

    You use interfaces.You'll type a bit more, and multiple-inheritance would help in this specific use case, but it's not worth it.


  • Banned

    @xaade said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    @blakeyrat said in I have no multiple inheritance and I must scream:

    @mikehurley Yeah but imagine all the compiler contortions they put in to make yield return x; work. It's not like they were lazy when making the compiler.

    They didn't make multiple inheritance not because it's hard, but because it's a very bad idea.

    Understood.

    But how hard would it be for them to cook up something like a formal composite that automates this.

    You know that "every feature starts with -100 points" thing? I don't see any use case that's common enough to make this feature desirable, and that can't be solved almost as well with what's currently available in the language. We could have every feature imaginable, but it wouldn't end up good - the language would be very bloated, with many overlapping features that would result in so many "right ways to do" that barely any libraries would be compatible with each other. Look at C++! It has so many features, and even if they're all good for their specific use cases, the whole language became incomprehensible mess.

    Languages should only have features that are actually very helpful to a wide group of users (like extension methods), or that let you do things that are literally impossible to do with existing features (like dynamic types).


  • And then the murders began.

    @gąska said in I have no multiple inheritance and I must scream:

    Languages should only have features that are actually very helpful to a wide group of users (like extension methods), or that let you do things that are literally impossible to do with existing features (like dynamic types).

    Even things being impossible otherwise doesn't convince me that dynamic types were a good idea.



  • @unperverted-vixen said in I have no multiple inheritance and I must scream:

    Even things being impossible otherwise doesn't convince me that dynamic types were a good idea.

    They're a practical idea in a world where JavaScript is the most common language and JSON (unlike XML) has no concept of a "fixed schema".


  • Banned

    @blakeyrat you can have fixed-format JSON files, just like you can have freeform XML. Just because there's no IEEE RFC for it doesn't mean you can't do it.


  • Considered Harmful


  • Notification Spam Recipient

    @xaade said in I have no multiple inheritance and I must scream:

    @mrl said in I have no multiple inheritance and I must scream:

    If I need to create additional interfaces, abstract classes, etc, and write 50 lines of new code, to avoid repeating 5 lines... does it make sense? I often find that simplicity is just better.

    Depends on whether those 5 lines of code need to match,

    Just add a comment, so others know what's going on.

    and whether this is a major single-point-of-failure.

    And whether it's worse than multiple points of failure.

    I have a situation like that in my current project. We are refactoring a module, which got bloated and hard to understand over the course of development. There were two competing ideas for this refactoring:

    1. Extract complexity to external configuration storage, make controlling class call methods in the module based on this configuration.
      Pros: 2000 lines of code becomes 200, no code repetition, easy to understand, modifiable without deployment.
      Cons: It's not 'beautiful'.

    2. Abstract classes, interfaces, factories, branching-on-type, etc.
      Pros: It's so so 'beautiful'.
      Cons: 2000 lines becomes 5000, code repetition still remains, impossible to understand without guidance, modification requires deployment.

    Guess which won.



  • @gąska said in I have no multiple inheritance and I must scream:

    Look at C++! It has so many features, and even if they're all good for their specific use cases, the whole language became incomprehensible mess.

    The reason C++ is an incomprehensible mess has little to do with the amount of features they put into the language. C# has shittons of language features as well. In fact, C++ is currently becoming easier to use precisely because they added missing features.


  • Fake News

    Unlike the more complicated questions in the help section, this topic has been dragged to the bikeshed and back but I couldn't resist adding my own ¢50:

    @blakeyrat said in I have no multiple inheritance and I must scream:

    I'm building a bunch of POCO (plain ol' C# objects) classes to serve as an API interface to JavaScript clients. A lot of these POCOs contain the same sets of fields, and so I've been using simple inheritance to implement that.

    @blakeyrat said in I have no multiple inheritance and I must scream:

    1. Create two versions of APIReportRequest, one of which is authenticated and one of which isn't. This is my current front-runner idea, but generally duplicating code is a major WTF. (In this case it's less of a WTF because the duplication is just the models and not the actual methods that operate on them.)

    2. Since AuthenticatedAPIRequest only contains two fields, copy them into every report request manually. This breaks my current authentication code, which expects to be passed an object that inherits from AuthenticatedAPIRequest, but could be made to work with an override or something. Also: more code duplication.

    Reserve inheritance for the places where you want to express "this behaves like a ..." rather than "this class has a ...".

    You seem to have an irrational fear of code duplication. Duplication is not a WTF in POCOs meant for data transfer; what's to say that a field might not change its meaning along the way? If that ever happens, you'll be handling special cases in code which receives the base class anyway.

    I would thus duplicate nearly all properties, then maybe make an interface out of the AuthenticatedAPIRequest class to indicate that "this POCO has a token" for those few places where you want to pass part of a request to a shared function.

    Once you're inside the classes receiving the POCOs as a function parameter you can still decide to translate everything into 1 object (we tend to use AutoMapper for such boilerplate).



  • @pie_flavor if you want a fixed schema, why not a binary format? the point of wasting bits with these silly tags is supposed to make things schemaless



  • @dfdub said in I have no multiple inheritance and I must scream:

    The reason C++ is an incomprehensible mess has little to do with the amount of features they put into the language.

    Binary incompatibilty between runtime and compiler versions, no guarantee that the header matches a lib (and maybe some ifdef breaking the compatibility there). Hard to parse, there are few decent editors with intelissense for it that parse templates correctly. And then you have the WTF language features. And the WTFs in its standard library.


  • Banned

    @dfdub said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    Look at C++! It has so many features, and even if they're all good for their specific use cases, the whole language became incomprehensible mess.

    The reason C++ is an incomprehensible mess has little to do with the amount of features they put into the language. C# has shittons of language features as well. In fact, C++ is currently becoming easier to use precisely because they added missing features.

    Yes, it's not just about a number of features. It's also about how they are implemented, and how they interact with each other. Every single feature C++ has added is a net benefit over C, but their combination is what makes the language so hard. They added features with no regard how it fits the rest of language, and for each problem they added a hack to work around it. C# has many features too, but they actually thought it all through, and pieces fit together. And to be honest, C# is less powerful than C++, in the sense that things you can theoretically achieve in C++ are a strict superset of what you can do in C#.



  • You could make AuthenticatedAPIRequest an attribute and check for a token in the handler that parses the request.



  • @gąska said in I have no multiple inheritance and I must scream:

    They added features with no regard how it fits the rest of language, and for each problem they added a hack to work around it.

    I wouldn't say that. I think a more accurate description would be that they wanted 100% compatibility to C at the source code level and that's where 90% of the flaws in the language come from.

    It's a good thing that the standards committee started thinking about C++ as a language mostly separate from C now that needs to evolve independently. std::string_view, std::variant and std::unique_ptr are just the first steps in a gradual migration towards a separate language that merely has a compatibility layer for C. And constexpr is already making some of the template abuse unnecessary.


  • Banned

    @dfdub said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    They added features with no regard how it fits the rest of language, and for each problem they added a hack to work around it.

    I wouldn't say that. I think a more accurate description would be that they wanted 100% compatibility to C at the source code level and that's where 90% of the flaws in the language come from.

    Then they added initializer lists which reuse the "explicit" keyword for turning type inference on and off, so you can't have {1} magically becoming object of desired type without 1 doing that too.

    When they added lambda expressions which are always used locally and almost always with incredibly long argument types like std::vector<MyClass>::const_iterator they also introduced auto keyword that lets you skip that, but didn't let you use auto in lambda.

    It's not just that there are too many features. It's that they add new ones with little regard of how they interact with the rest of the system. And in the one case they did think it through - rvalue references and move semantics - the ruleset they came up with is so convoluted - by necessity! - that you cannot write code that takes full advantage of move semantics without descending into madness, and taking your code with you so no other programmer can decipher it.



  • We're getting seriously OT here (sorry blakeyrat!), but one last reply:

    @gąska said in I have no multiple inheritance and I must scream:

    they also introduced auto keyword that lets you skip that, but didn't let you use auto in lambda.

    It's not just that there are too many features. It's that they add new ones with little regard of how they interact with the rest of the system.

    Why do you think the standardization process takes so long? Because that's exactly what they don't do.

    And in the one case they did think it through - rvalue references and move semantics - the ruleset they came up with is so convoluted - by necessity! - that you cannot write code that takes full advantage of move semantics without descending into madness, and taking your code with you so no other programmer can decipher it.

    That statement is so obviously wrong to anyone who ever actually worked with C++1x that I don't even know what to say.


Log in to reply