1

I'm starting with C# and having trouble writing a method that accepts both Vector2 and Vector3 arguments in C#.

Generic methods looked like the way to go, but I can't make it work just yet. Here's what I tried:

static void GetNoisePosition<T>(ref T position, float offset, float scale) where T : IEquatable<T>
{
    position += position.GetType().one * (offset + 0.1f);
    position *= scale;
}

I dont really want to have 2 versions of GetNoisePosition, each taking a vector type, as I dont want to duplicate the logic, and it'd be hard to create another method that would share some of this logic.

So, the issue is that I want to call the one method on the class of type T, but it's telling me that I can't.

Can I get access to the class via the position instance and call one on it?

Robin
  • 21,667
  • 10
  • 62
  • 85
  • `position.GetType().GetProperty("one").GetValue(null)` – Emanuel Vintilă Apr 16 '20 at 13:18
  • 1
    *"I dont really want to have 2 versions of GetNoisePosition"* - why? If they have something common, that common could be a part of base class or interfaces. Otherwise you are dealing with 2 different types and accessing their completely different properties. Method overload is the way. – Sinatr Apr 16 '20 at 13:21
  • Or, just use `Vector3 position`, since `Vector2` is implicitly convertible to `Vector3` – Emanuel Vintilă Apr 16 '20 at 13:22
  • Nevermind, I just noticed that it's a `ref` parameter – Emanuel Vintilă Apr 16 '20 at 13:23
  • `position.GetType().GetProperty("one").GetValue(null)` is fine in itself, but I then can't use the `*` method on it. – Robin Apr 16 '20 at 13:24
  • @Sinatr so let's say I go with method overloading, I have to duplicate the logic? – Robin Apr 16 '20 at 13:24
  • And I mean, vectors in 2 or 3 dimensions respond to pretty much all the same methods, I dont really see why i should have 2 methods for that. In Ruby it would be so trivial :( – Robin Apr 16 '20 at 13:26
  • @Robin, yes, or you can move calcuation into common method, which both calls, it could have same name: `float GetNoisePosition(float position, float offset, float scale)`.. Btw, your void method shouldn't be called `Get...` rather `Calculate...` – Sinatr Apr 16 '20 at 13:28
  • Ok, it just doesnt seem practical, as it forces me to work per axis, and not just take advantage of the fact that vector2 and vector3 respond to the same methods. It's hard for me to believe there wouldnt be a way in c# to have a method like this one. – Robin Apr 16 '20 at 13:33
  • Why can you not use the * operator? Cast the `one` back to `T` and it should work – Emanuel Vintilă Apr 16 '20 at 13:33
  • @EmanuelVintilă https://imgur.com/a/GNWGRDE – Robin Apr 16 '20 at 13:36
  • Right... I'll be back with a complete solution in a few minutes. It's gonna involve more reflection, so it might not be appropriate for your use case – Emanuel Vintilă Apr 16 '20 at 13:37

1 Answers1

2

Getting the type of the vector, and the operator methods using reflection:

public static void CalculateNoisePosition<T>(ref T position, float offset, float scale)
{
  Type vector = position.GetType();
  MethodInfo add = vector.GetMethod("op_Addition", new[] {typeof(T), typeof(T)});
  MethodInfo multiply = vector.GetMethod("op_Multiply", new[] {typeof(T), typeof(float)});

  T one = (T) vector.GetProperty("one").GetValue(null);

  position = (T) add.Invoke(null, new object[] {position, multiply.Invoke(null, new object[] {one, offset + 0.1f})});
  position = (T) multiply.Invoke(null, new object[] {position, scale});
}

Note that if you call this method with T being anything else other than Vector2 or Vector3, you will most likely get a NullReferenceException.

As always when there's reflection involved, please profile the code and decide whether it's worth it to use this approach rather than write 2 almost identical methods.

Emanuel Vintilă
  • 1,911
  • 4
  • 25
  • 35
  • Thanks for taking the time to do that. I'm definitely not using that in place of 2 simple methods, but I might learn something from it. – Robin Apr 16 '20 at 16:23
  • Last question real quick: let's say I have a simple declaration `static void GetNoisePosition(ref T position)`. I can't simply call methods on `position`? Even if I just use `position.x`, it doesnt work. – Robin Apr 16 '20 at 16:25
  • No, because again, you don't know `T`. You would have to get all the properties using reflection. You also can't use type constraints because `Vector2` and `Vector3` don't share a common interface, and they are structs, so they also don't have a common base class. – Emanuel Vintilă Apr 16 '20 at 16:50
  • My suggestion is to remove the `ref` specifier from `position` and instead return the calculated value. Then, instead of `T`, type it as `Vector3`, because `Vector2` is implicitly convertible to `Vector3` – Emanuel Vintilă Apr 16 '20 at 16:52
  • Or if you really want you can have another overload with `Vector2` that calls the overload with `Vector3` – Emanuel Vintilă Apr 16 '20 at 16:53