2

I have a generic class CGeometryCalibration2D<T> which can be used with numeric types like int, double, float, and my custom type CLocation.
(less important) question 1: how can I restrict T to one of these types?

Inside this class, there is a function

double InterpolateReverse (T i_Value);

which should be used only with CLocation as T.

I don't know any way to apply such a restriction.
I already tried extension methods, which would do exactly this restriction

double InterpolateReverse (this CGeometricCalibration2D<CLocation>, CLocation i_Value);

but they don't allow access to private members. I could work around this limitation by using Reflection, but that's not the nicest way.

What can I do here?
Should I maybe find a completely different approach?

The only remaining idea I have is overloading the generic class by concrete implmentations and adding the function there, like

CGeometricCalibration2D_CLocation : CGeometricCalibration2D<CLocation>
{
  double InterpolateReverse (CLocation i_Value);
}

but then I need to have an object of the concrete type CGeometricCalibration2D_CLocation in order to execute InterpolateReverse ()

Tobias Knauss
  • 3,361
  • 1
  • 21
  • 45
  • What you trying to achieve with generics in your case? You have concrete implementations where you will use this method with restriction for correspondent type – Fabio Aug 23 '17 at 10:16
  • You can't restrict generics to primitive types like int (UInt32) and their like. You can restrict it to any struct type (including int) and to your custom CLocation type – Ofer Barasofsky Aug 23 '17 at 10:18
  • @Fabio The class itself my be used with different types. But there is one function that makes only sense to be used with `CLocation`. – Tobias Knauss Aug 23 '17 at 10:19
  • @TobiasKnauss, that mean that your class cannot be generic. You should move that function to another class or redesign whole class to restrict it to only one type. If you stick with your current approach you will end with "hacky" workarounds, which will bite you in the future where you will need to invent another workarounds over existed ones – Fabio Aug 23 '17 at 10:28
  • @Fabio: I see what you mean, and basically I agree. But how can I design a class that takes different types but basically works with the same code, without doing code duplication? IMHO I'd need a generic base class then and concrete overloads, like mentioned at the end of my question...? – Tobias Knauss Aug 23 '17 at 10:30
  • @TobiasKnauss, reason for my opinion is _But there is one function that makes only sense to be used with `CLocation`_ - this explanation break a little what generic classes are used for. Without actual context of the class it difficult to say something. – Fabio Aug 23 '17 at 10:55

5 Answers5

2

I have a generic class CGeometryCalibration2D<T> which can be used with numeric types like int, double, float, and my custom type CLocation.

That doesn't sound like a generic type.

how can I restrict T to one of these types.

You can't, there is no constraint that encompasses those types.

Inside this class there is a function (...) that should only be used with CLocation as T.

Easy, don't make the method generic because it isn't. Simply write a single overlord with a CLocation argument.

Remember, generic means generic. A generic class or method should be able to work with an infinite set of generic type parameters. When the valid types are limited chances are that you are doing something wrong.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • 'When the valid types are a bound set...' true, except where a restriction to generics is intentionally applied. I wish there was a restriction to numeric types. // But same question to you as to Fabio in the 10:30h comment on my original post: How can I create a class that runs with different types without code duplication? I think I have to use a (abstract) generic base class. – Tobias Knauss Aug 23 '17 at 10:47
  • @TobiasKnauss Can you show me a valid c# constraint that guarantees a bound set of generic type arguments? – InBetween Aug 23 '17 at 10:49
  • you could use a custom interface as a constraint as suggested by Darjan Bogdan in his answer. But that still does not help me much. – Tobias Knauss Aug 23 '17 at 10:51
  • @InBetween: While I agree with your answer (basically same as everyone else), `where` clause is the *valid c# constraint that guarantees a bound set of generic type arguments*. However it won't work in this case because 1. the types are value types, 2. you can't do OR between multiple types. – dotNET Aug 23 '17 at 10:52
  • 1
    @dotNET a `where` clause does not guarantee a bound set at all. There are potentially infinite types that can comply with any `where` clause. – InBetween Aug 23 '17 at 10:54
  • @InBetween: Your definition of "bound" is different from mine. `where` **binds** type parameters to implement certain members, so you could be sure at compile-time that a certain call on that parameter would work. That's all there is to be "bound" to. – dotNET Aug 23 '17 at 10:56
  • @TobiasKnauss and exactly how many types can implement your interface? 1, 2 or infinite? The point remains the same, if the set of types is finite (as is your case), then probably generics is not the right choice. Now, answering your other question, if the valid types have no commonality you can leverage then overloading is your only reasonable option. – InBetween Aug 23 '17 at 10:57
  • @dotNET ah, sorry, English is not my native language. I thought *bound set* meant a *finite* set. Sorry for the confusion. – InBetween Aug 23 '17 at 10:58
  • How is this solved in the .net framework? Are there such classes at all, that have common functionalilty, but work with different types and are NOT generic? – Tobias Knauss Aug 23 '17 at 11:04
  • @TobiasKnauss `Enumerable.Sum` is a great example. It uses overloading for all valid primitive types. – InBetween Aug 23 '17 at 11:07
  • Thanks, but this is probably such a primitive function that there are no large common code sections like in my situation. I think I am using a **abstract generic base class for all common functionality** to avoid copy&paste, and concrete implementations for the usable types. This maybe gives the cleanest solution, especially since I will not instantiate this class with unknown T, so I can always use the concrete implementation. – Tobias Knauss Aug 23 '17 at 11:13
  • 1
    @TobiasKnauss but what common implementation are you going to be able to implement in your base class if there is no commonality you can leverage to begin with? `T` unconstrained is as good as `object`. You can't add, multiply, etc. two `T`s – InBetween Aug 23 '17 at 11:15
  • I can (no typing error!) add, multiply, etc. using generics. There are a couple of workarounds on the internet (e.g. http://stackoverflow.com/q/63694). Also there is functionality for saving/loading data to/from XML files, which I also have made generic where needed. That's why there indeed are large common sections, that I don't want to copy. – Tobias Knauss Aug 23 '17 at 11:22
  • 1
    @TobiasKnauss then I'd make my generic common implementation a private detail of my class and not expose it. I'd still design my public API through overloads that would delegate to the generic private implementation where possible. This way I don't have to pay the price of the type unsafety you have right now which obliges you to throw at runtime if `T` happens to be something you can't manage. – InBetween Aug 23 '17 at 11:28
1

You can achieve it by defining additional type parameter on that method, and add type constraint. In order to achieve this, you'll need to create ICLocation interface

public class CGeometryCalibration2D<T> where T: struct
{
    public double InterpolateReverse<V>(V i_Value) where V: struct, ICLocation
    {
        return default(double);
    }
}

public interface ICLocation { }

public struct CLocation : ICLocation { }

Example of usage:

var geoInt = new CGeometryCalibration2D<int>();
geoInt.InterpolateReverse(12); //Compile time error
var loc = new CLocation();
geoInt.InterpolateReverse(loc);
Darjan Bogdan
  • 3,780
  • 1
  • 22
  • 31
  • but how do I say that V == T? The underlying type T of the class must be the same type as used in `InterpolateReverse()` – Tobias Knauss Aug 23 '17 at 10:21
  • if you need to check type due to business logic, you could use `typeof` operator, e.g. `typeof(T) == typeof(V)` – Darjan Bogdan Aug 23 '17 at 10:23
  • There is no other way to achieve exactly what you want, this solution certainly has cons, but it's the closest to your needs. – Darjan Bogdan Aug 23 '17 at 10:24
  • But in your solution I could execute `InterpolateReverse()` on CGeometryCalibration with every T. So I just could check if T has the right type, no need for V. And that's what I am already doing. // BTW: how would you get a compile time error on your example above? – Tobias Knauss Aug 23 '17 at 10:27
  • Because of the `ICLocation` interface, interface is the most important part of the solution, it "demands" that the input parameter must implement that interface. That's why you need to implement it in your `CLocation` type. You will be able to invoke `InterpolateReverse` method only with T parameters which implement ICLocation interface. – Darjan Bogdan Aug 23 '17 at 10:49
  • okay, I see. But in your example, the `geoInt.InterpolateReverse(loc)` is possible, although it shouldn't. It'd fail during runtime, and I'd prefer a solution that catches this situation during compile time. However, +1, and thanks a lot for your answer, it has helped me find a better way on my original problem. :) – Tobias Knauss Aug 23 '17 at 10:57
0

You cannot put a value type constraint on generic parameters, as documented here.

What you could do is create overloads of your function instead of using generics, e.g

double InterpolateReverse (int i_Value);
double InterpolateReverse (double i_Value);
double InterpolateReverse (float i_Value);
Owen Pauling
  • 11,349
  • 20
  • 53
  • 64
  • But the overloaded type must match the `T` of the class. So I would need to throw exceptions during runtime if the wrong method is called. – Tobias Knauss Aug 23 '17 at 10:16
0

For the first question, you cannot use multiple types for one generic parameter. Think about it. How will the compiler decide the compile-time type of the parameter in that case? You should use overloaded methods for each of the types that you want to support and simply remove that generic parameter.

This should solve your second problem as well.

dotNET
  • 33,414
  • 24
  • 162
  • 251
  • I will not have multiple types. I will have ONE OF THE mentioned types for `T`. – Tobias Knauss Aug 23 '17 at 10:18
  • Yeah, but compiler has to be ready for ANY of those types then, which is not possible. – dotNET Aug 23 '17 at 10:18
  • The class runs fine with any of those types. But this single function only works well with `CLocation`. I want to make `InterpolateReverse` to be used only with CLocation, also at compile time, not just during runtime by on-the-fly type-check. – Tobias Knauss Aug 23 '17 at 10:24
0

Thank you all for your answers and comments!
After thinking about them, I decided to mix generics with inheritance, which hopefully is the cleanest solution to my problem.
The abstract generic base class contains all that stuff that applies to all usable types of T, but it isn't instantiable. The concrete implementations ensure that only those T are usable that make sense to be used.

public abstract class CGeometryCalibration2D<T> : CGeometryCalibration<T>
{
  ctor()
  public override void Clear ()
  public bool Add (double i_dPosition, CGeometryCalibration1D<T> i_oGeoCalib1D)
  public bool Remove (double i_dPosition)
  public override void CopyTo (ref CGeometryCalibration<T> io_oGeoCalib)
  public override T Interpolate (CLocation i_locPosition, bool i_bExtrapolate = false)
  public T Interpolate (double i_dPosition, double i_dPositionGC1D, bool i_bExtrapolate = false)
  public override CResulT Load (CXmlDocReader i_xmldocreader)
  public override CResulT Save (CXmlDocWriter i_xmldocwriter)
}

public class CGeometryCalibration2D_Double : CGeometryCalibration2D<double>
{
  ctor()
}

public class CGeometryCalibration2D_CLocation : CGeometryCalibration2D<CLocation>
{
  ctor()
  public double InterpolateReverse (CLocation i_locValue, out CLocation o_ValueInterpolated, bool i_bExtrapolate = false)
}
Tobias Knauss
  • 3,361
  • 1
  • 21
  • 45