Question

I have a big object:

class BigObject{
    public int Id {get;set;}
    public string FieldA {get;set;}
    // ...
    public string FieldZ {get;set;}
}

and a specialized, DTO-like object:

class SmallObject{
    public int Id {get;set;}
    public EnumType Type {get;set;}
    public string FieldC {get;set;}
    public string FieldN {get;set;}
}

I personally find a concept of explicitly casting BigObject into SmallObject - knowing that it is a one-way, data-losing operation - very intuitive and readable:

var small = (SmallObject) bigOne;
passSmallObjectToSomeone(small);

It is implemented using explicit operator:

public static explicit operator SmallObject(BigObject big){
    return new SmallObject{
        Id = big.Id,
        FieldC = big.FieldC,
        FieldN = big.FieldN,
        EnumType = MyEnum.BigObjectSpecific
    };
}

Now, I could create a SmallObjectFactory class with FromBigObject(BigObject big) method, that would do the same thing, add it to dependency injection and call it when needed... but to me it seems even more overcomplicated and unnecessary.

PS I'm not sure if this is relevant, but there will be OtherBigObject that will also be able to be converted into SmallObject, setting different EnumType.

Was it helpful?

Solution

None of the other answers have it right in my humble opinion. In this stackoverflow question the highest-voted answer argues that mapping code should be kept out of the domain. To answer your question, no - your usage of the cast operator is not great. I would advise to make a mapping service which sits between your DTO and you domain object, or you could use automapper for that.

OTHER TIPS

It is... Not great. I've worked with code that did this clever trick and it led to confusion. After all, you would expect to be able to just assign the BigObject into a SmallObject variable if the objects are related enough to cast them. It doesn't work though - you get compiler errors if you try since as far as the type system is concerned, they're unrelated. It is also mildly distasteful for the casting operator to make new objects.

I would recommend a .ToSmallObject() method instead. It is clearer about what is actually going on and about as verbose.

While I can see why you would need to have a SmallObject, I would approach the problem differently. My approach to this type of issue is to use a Facade. Its sole purpose is to encapsulate BigObject and only make available specific members. In this way, it is a new interface on the same instance, and not a copy. Of course you may also want to perform a copy, but I would recommend that you do so through a method created for that purpose in combination with the Facade (for instance return new SmallObject(instance.Clone())).

Facade has a number of other advantages, namely ensuring that certain sections of your program can only make use of the members made available through your facade, effectively guaranteeing that it cannot make use of what it shouldn't know about. In addition to this, it also has the enormous advantage that you have more flexibility in changing BigObject in future maintenance without having to worry too much about how it is used throughout your program. So long as you can emulate the old behavior in some form or another, you can make SmallObject work the same as it did before without having to change your program everywhere BigObject would have been used.

Note, this means BigObject does not depend on SmallObject but rather the other way around (as it should be in my humble opinion).

There is a very strong convention that casts on mutable reference types are identity-preserving. Because the system generally does not allow user-defined casting operators in situations where an object of the source type could be assigned to a reference of the destination type, there are only a few cases where user-defined casting operations would be reasonable for mutable reference types.

I would suggest as a requirement that, given x=(SomeType)foo; followed sometime later by y=(SomeType)foo;, with both casts being applied to the same object, x.Equals(y) should always and forevermore be true, even if the object in question was modified between the two casts. Such a situation could apply if e.g. one had a pair of objects of different types, each of which held an immutable reference to the other, and casting either object to the other type would return its paired instance. It could also apply with types that serve as wrappers to mutable objects, provided that the identities of the objects being wrapped were immutable, and two wrappers of the same type would report themselves as equal if they wrapped the same collection.

Your particular example uses mutable classes, but does not preserve any form of identity; as such, I would suggest that it is not an appropriate usage of a casting operator.

It might be okay.

A problem with your example is that you use such example-ish names. Consider:

SomeMethod(long longNum)
{
  int num = (int)longNum;
  /* ... */

Now, when you've a good idea what a long and int means, then both the implicit cast of int to long and the explicit cast from long to int are quite understandable. It's also understandable how 3 becomes 3 and is just another way to work with 3. It's understandable how this will fail with int.MaxValue + 1 in a checked context. Even how it will work with int.MaxValue + 1 in an unchecked context to result in int.MinValue isn't the hardest thing to grok.

Likewise, when you cast implicitly to a base type or explicitly to a derived type its understandable to anyone who knows how inheritance works what is happening, and what the result will be (or how it might fail).

Now, with BigObject and SmallObject I don't have a sense of how this relationship works. If your real types where such that the casting relationship where obvious, then casting might indeed be a good idea, though a lot of the time, perhaps the vast majority, if this is the case then it should be reflected in the class hierarchy and the normal inheritance-based casting will suffice.

Licensed under: CC-BY-SA with attribution
scroll top