1

I'm making a Vector class in C#, with a normalized parameter, which is the vector normalized. However, when I'm trying to calculate the normalized Vector, I get a stack overflow exception. Is there a better way to do it? Here is my code (inside the constructor):

namespace Vector3
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Vector3 vector = new(3, 4, 0);
            Console.WriteLine(vector.magnitude);
            Console.WriteLine(vector.normalized);
        }
    }



    class Vector3
    {
    
        public float x, y, z;
        public readonly float magnitude;
        public readonly Vector3 normalized;

        public Vector3(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;

            this.magnitude = Convert.ToSingle(Math.Sqrt(Math.Pow(this.x, 2) + Math.Pow(this.y, 2) + Math.Pow(this.z, 2)));
            this.normalized = new Vector3(this.x/this.magnitude, this.y/this.magnitude, this.z/this.magnitude);
            
        }
    }
}
Woul
  • 21
  • 3
  • Please provide a [mcve]. It's really hard to help with just a couple of lines of code. – Jon Skeet May 02 '23 at 14:22
  • (And are you really using C# 4, which came out in 2010? Unless your question is specific to C# 4, I'd just tag it c#...) – Jon Skeet May 02 '23 at 14:22
  • @JonSkeet My bad. I'm not using cs4, I'm using the latest .net core. I'll post the rest of my code. – Woul May 02 '23 at 16:57
  • 3
    Okay, you're calling the `Vector3` constructor *within* the `Vector3` constructor, unconditionally. I'm not surprised that creates a StackOverflowException... how do you expect that constructor call to ever complete? (The "outer" one will make an "inner" constructor call, which in turn will make *another* constructor call, etc...) – Jon Skeet May 02 '23 at 17:09

2 Answers2

3

You have infinite recursion in the constructor. While you creating Vector3, you create another Vector3 inside the constructor for normalized value. And this cycle never ends and finally gives you StackOverflowException

You can use lazy creation of normalized value as shown below.

using System;

namespace Vector3
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Vector3 vector = new(3, 4, 0);
            Console.WriteLine(vector.magnitude);
            Console.WriteLine(vector.normalized);
        }
    }

    class Vector3
    {
        public readonly float x, y, z;
        public readonly float magnitude;
        
        private Vector3 _normalized = null;
        public Vector3 normalized => _normalized ??= new(this.x/this.magnitude,
                                               this.y/this.magnitude, this.z/this.magnitude);

        public Vector3(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;

            this.magnitude = Convert.ToSingle(Math.Sqrt(Math.Pow(this.x, 2) + Math.Pow(this.y, 2) + Math.Pow(this.z, 2)));
        }
    }
}
Serg
  • 3,454
  • 2
  • 13
  • 17
  • This solution worked as expected, but I'm unfamiliar with this syntax. Can you explain? – Woul May 02 '23 at 17:37
  • What part of the syntax needs additional commenting? In general, here we have the `normalized` property (not a field), which returns the value of the `_normalized` field if such value exists. And if the `_normalized` field is `null`, then the value will be created, stored into the `_normalized` field and then returned (this is what the `??=` operator is used for). – Serg May 02 '23 at 17:42
  • @Woul: `?==` is a "null-coalescing operator": https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-coalescing-operator, and `=>` is a "lambda expression": https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions – paulsm4 May 02 '23 at 17:46
  • So the ??= operator checks if a value is null, right? – Woul May 02 '23 at 17:47
  • 1
    Yes, the "??=" checks if a value is null... and then "lets you do something" ("coalesce the value") if it is. For more details, please read the links I cited above. – paulsm4 May 02 '23 at 17:49
  • Got it! Thank you all so much for helping me – Woul May 02 '23 at 17:51
2

However, when I'm trying to calculate the normalized Vector, I get a stack overflow exception

You are creating a new Vector3 inside the Vector3 ctor, which leads to "infinite" recursive calls ending in StackOverflow exception. Remove the call or make it some kind of lazy initialization. Or expose it with separate GetNormalized method.

Another option is to "break" the recursion, for example you can create another private ctor and pass isNormalized parameter (which would work better with readonly public contract):

class Vector3
{
    public float X { get; }
    public float Y { get; }
    public float Z { get; }
    public float Magnitude { get; }
    public Vector3 Normalized { get; }

    public Vector3(float x, float y, float z):this(x,y,z, false)
    {
    }

    private Vector3(float x, float y, float z, bool isNormalized)
    {
        X = x;
        Y = y;
        Z = z;

        Magnitude = Convert.ToSingle(Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2) + Math.Pow(Z, 2)));
        Normalized = isNormalized ? this : new Vector3(X/Magnitude, Y/Magnitude, Z/Magnitude, true);
    }
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132