-1

Consider the following classes; Getter and Caster where Getter retrieves a value T. Caster is a wrapper for Getter that casts the retrieved value and is a Getter itself.

public abstract class Getter<T>
{
    public abstract T Get();
}

public class Caster<TIn, TOut> : Getter<TOut>
{
    private Getter<TIn> getter;

    public Caster(Getter<TIn> getter)
    {
        this.getter = getter;
    }

    public TOut Get()
    {
        return (TOut)(object)getter.Get();
    }
}

The caster uses boxing creates garbage every time Get is called. Is there any way to cache the box and change the value or something to avoid the garbage? Maybe there's a better solution than boxing?

Martin
  • 131
  • 2
  • 8
  • 1
    `Caster` has an implicit constraint of `where TIn : TOut`. `Caster` will fail at runtime because you have to unbox `int` before *converting* to `long`. You need a converter, not a caster. – madreflection Jul 02 '19 at 22:45
  • 1
    Could you please clarify if by "boxing" you actually mean boxing (casting struct to object) or just cast (as even considering `TIn : TOut` means you are not dealing with value types)? Showing an example where you believe this code is a problem would help. – Alexei Levenkov Jul 02 '19 at 22:46
  • Whatever you are doing in the getter, so it in the setter instead. Then the 'garbage' gets created just the once. – mjwills Jul 02 '19 at 23:16
  • @madreflection what do you mean by "converter"? – Martin Jul 03 '19 at 12:56
  • **What:** Either (A) your `Get` method has to be implemented as `return (TOut)Convert.ChangeType(getter.Get(), typeof(TOut));`, making this an over-engineered wrapper on that mechanism, or (B) you have to add `where TIn : TOut` as a generic type constraint, making this an over-engineered way of casting to a base class / interface. The former is conversion. The latter is casting. [@V0ldek](/u/4646738) gave a great explanation of the difference, including exactly why the `Caster` case I mentioned would fail. – madreflection Jul 03 '19 at 16:37
  • **Why:** The compiler and the runtime can't convert between arbitrary types without help. True casting allows you to look at an instance as a different type *in its type hierarchy*. The compiler will do *conversion* between known primitive types for which a conversion is defined, e.g. between `int` and `long`, and it reuses the *cast syntax* for both that and unboxing, causing some of the confusion here. For any other conversion, at least one of the types involved has to enlist in a known mechanism, which declares that the conversion is meaningful and possible. – madreflection Jul 03 '19 at 16:37
  • `IConvertible` is one such mechanism; type converters are another (MVC uses these for parameter binding). You could also define your own mechanism (if you wanted) and provide built-in definitions for types you know, but you would also need to publish a way for types to enlist in it. A utility like `Getter/Setter` is only useful if it somehow simplifies one of those mechanisms or unifies two or more of them. V0ldek's answer may not be what you wanted or expected but it's a solid analysis of your code; you should accept it. – madreflection Jul 03 '19 at 16:37
  • The code I have posted is the most simple way to present a problem I have in some very complicated code for a complicated problem that you will have to assume is complicated for a good reason. IConvertible is only implemented by value types. Value types are a very small subset of all possible types that could be passed as a generic argument to the caster, so I assume you mean I should add a check at the top of my Get() method that getter.Get() is IConvertible and use Convert or boxing respectively. But I'm still using boxing and that still generates garbage, so my question is not answered. – Martin Jul 03 '19 at 17:10

1 Answers1

1

You're doing some bad things to the type system. Consider what happens with a Caster<int, long>. You cast int to an object, which causes a boxing operation. Then you try to cast that boxed int to a long. That will throw a InvalidCastException (see here), because unboxing has to be explicit - instead of unboxing an int the runtime tries to cast the object to a long. Your class is just unusable with value types because of how boxing works. If you want to enable conversion between value types, you might want to take a look at the IConvertible interface.

V0ldek
  • 9,623
  • 1
  • 26
  • 57
  • Yeah, I'm with you on the first part. But I want to use it for types that do not necessarily implement IConvertible as well. – Martin Jul 03 '19 at 14:29
  • Well, then you're basically trying to do a runtime cast on a value type. All a runtime cast does is treat a reference as a reference to something else. It's possible to do that at runtime for reference types, provided you actually intend to use a type as itself or as one of its predecessors in the inheritance hierarchy (or an interface it implements). It can never make sense to interpret something as of a value type `T` if it's not indeed `T`. At the very least `TOut` has to be a reference type for this approach to make sense. – V0ldek Jul 03 '19 at 15:25