2

It is common to read around that object casting is a bad practice and should be avoided, for instance Why should casting be avoided? question has gotten some answers with great arguments:

  1. By Jerry Coffin:

    Looking at things more generally, the situation's pretty simple (at least IMO): a cast (obviously enough) means you're converting something from one type to another. When/if you do that, it raises the question "Why?" If you really want something to be a particular type, why didn't you define it to be that type to start with? That's not to say there's never a reason to do such a conversion, but anytime it happens, it should prompt the question of whether you could re-design the code so the correct type was used throughout.

  2. By Eric Lippert:

    Both kinds of casts are red flags. The first kind of cast raises the question "why exactly is it that the developer knows something that the compiler doesn't?" If you are in that situation then the better thing to do is usually to change the program so that the compiler does have a handle on reality. Then you don't need the cast; the analysis is done at compile time.

    The second kind of cast raises the question "why isn't the operation being done in the target data type in the first place?" If you need a result in ints then why are you holding a double in the first place? Shouldn't you be holding an int?

Moving on to my question, recently I have started to look into the source code of the well known open source project AutoFixture originally devloped by Mark Seemann which I really appreciate.

One of the main components of the library is the interface ISpecimenBuilder which define an somehow abstract method:

object Create(object request, ISpecimenContext context);

As you can see request parameter type is object and by such it accepts completely different types, different implementations of the interface treat different requests by their runtime type, checking if it is something they cable dealing with otherwise returning some kind of no response representation.

It seems that the design of the interface does not adhere to the "good practice" that object casting should be used sparsely.

I was thinking to myself if there is a better way to design this contract in a way that defeats all the casting but couldn't find any solution.
Obviously the object parameter could be replaced with some marker interface but it will not save us the casting problem, I have also thought that it is possible to use some variation of visitor pattern as described here but it does not seem to be very scalable, the visitor will must have dozens of different methods since there is so many different implementations of the interface that capable dealing with different types of requests.

Although the fact that I basically agree with the arguments against using casting as part of a good design in this specific scenario it seems as not only the best option but also the only realistic one.

To sum up, is object casting and a very general contracts are inevitability of reality when there is a need to design modular and extendable architecture?

YuvShap
  • 3,825
  • 2
  • 10
  • 24
  • This question is, like the one you linked to, probably too broad to be [on-topic here](https://stackoverflow.com/help/on-topic). I think you might be better off at the [Software Engineering Stack Exchange](https://softwareengineering.stackexchange.com) - but check their FAQ first (I'm not 100% sure). – Wai Ha Lee Feb 03 '19 at 22:38
  • The answer is in the quote really. `That's not to say there's never a reason to do such a conversion, but anytime it happens, it should prompt the question of whether you could re-design the code so the correct type was used throughout.` – Kevin Gosse Feb 03 '19 at 22:40
  • From the answers in the other question, you could answer your own question with depends on your design and how you create your modular and extendable architecture. You might always know what kind of type you will have at hand and therefor there wouldn't be any need for the casting... – Icepickle Feb 03 '19 at 22:45
  • @KevinGosse as written, I asked myself the question but couldn't find any way to re-design the code, it does not mean that other ones couldn't provide other suggestions, which I hope to receive. – YuvShap Feb 03 '19 at 22:47
  • 1
    @YuvShap https://www.xkcd.com/1170/... You should really figure out what restriction made authors to use `object` and then see if in your particular use case(s) this reason no longer applies... (There is a very good chance that authors already spent significant time coming up with alternative solution and that is the only that worked so far) – Alexei Levenkov Feb 03 '19 at 23:06

1 Answers1

2

I don't think that I can answer this question generally, for any type of application or framework, but I can offer an answer that specifically talks about AutoFixture, as well as offer some speculation about other usage scenarios.

If I had to write AutoFixture from scratch today, there's certainly things I'd do differently. Particularly, I wouldn't design the day-to-day API around something like ISpecimenBuilder. Rather, I'd design the data manipulation API around the concept of functors and monads, as outlined here.

This design is based entirely on generics, but it does require statically typed building blocks (also described in the article) known at compile time.

This is closely related to how something like QuickCheck works. When you write QuickCheck-based tests, you must supply generators for all of your own custom types. Haskell doesn't support run-time casting of values, but instead relies exclusively on generics and some compile-time automation. Granted, Haskell's generics are more powerful than C#'s, so you can't necessarily transfer the knowledge gained from Haskell to C#. It does suggest, however, that it's possible to write code entirely without relying on run-time casting.

AutoFixture does, however, support user-defined types without the need for the user to write custom generators. It does this via .NET Reflection. In .NET, the Reflection API is untyped; all the methods for generating objects and invoking members take object as input and return object as output.

Any application, library, or framework based on Reflection will have to perform some run-time casting. I don't see how to get around that.

Would it be possible to write data generators without Reflection? I haven't tried the following, but perhaps one could adopt a strategy where one would write 'the code' for a data generator directly in IL and use Reflection emit to dynamically compile an in-memory assembly that contains the generators.

This is a bit like how the Hiro container works, IIRC. I suppose that one could design other types of general-purpose frameworks around this concept, but I rarely see it done in .NET.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • thanks a lot for your answer, the fact that AutoFixture is based on reflection does explain why ISpecimenBuilder return type is object, however I did not understand how it affects the input parameters types. Why The fact that the first parameter (request) type is object is dependent on reflection if reflection is used to generate the output. There must be other reason to define request as it is and theoretically it could be any other abstraction, so what is the motivation to define it as the highest possible abstraction in the language? am I missing something? – YuvShap Feb 18 '19 at 16:26
  • 1
    @YuvShap It's been a long time, so I don't remember all the details, but while I'd already been programming for more than a decade when I designed AutoFixture, there were things that I didn't know how to do. One problem was that I wanted to be able to request various things, such as `Type`, `PropertyInfo`, or `ParameterInfo`, as well as my own custom-types. There only type common to those is `Object`. These days, I might have decided to define a custom sum type encoded as a Visitor, but back then, I didn't realise that I could do that. – Mark Seemann Feb 19 '19 at 07:41
  • thank you for the detailed explanation which makes a lot of sense. – YuvShap Feb 19 '19 at 09:38