0

I'm fairly new to JavaScript and TypeScript.

As far as I'm concerned, TypeScript is compiled into regular JavaScript.

Coming from C#, where objects are passed by reference (if they're not primitive types or structs). I've written some TypeScript code to test something:

interface IRunner { 
    run(): string;
}

class TestDI {
    private readonly Runner: IRunner;

    constructor(runner: IRunner) {
        this.Runner = runner;
    }

    test(): void {
        console.log('Result of runner: ' + this.Runner.run());
    }
}

class FooRunner implements IRunner {
    run(): string {
        return 'Foo';
    }
}

class BarRunner implements IRunner {
    run(): string {
        return 'Bar';
    }
}

let runner: IRunner;
runner = new FooRunner();

const SUT: TestDI = new TestDI(runner);

// Expected output: Foo
// Actual output: Foo
console.log(runner.run());

// Expected output: Foo
// Actual output: Foo
SUT.test();

runner = new BarRunner();

// Expected output: Bar
// Actual output: Bar
console.log(runner.run());

// Expected output: Bar
// Actual output: Foo
SUT.test();

In the code above I've passed an instance of IRunner interface into a class c-tor, then, a field of my class is populated with whatever I passed in the c-tor.

Later in the code I change the variable I passed into the c-tor, however, when I call the test function Runner field of my class seems to not reflect any changes done on the original variable I passed when creating an instance of my TestDI class.

In C# the code I've written would behave as I expect it to. But here it's not.

What's going on?

Michael
  • 548
  • 6
  • 23
  • 1
    I don't understand why you'd expect `Result of runner: Bar` for the last `SUT.test()`. Assigning a new value to `runner`, whether or not it's an `IRunner`, **does not** change what `SUT.Runner` is. *"As far as I'm concerned, TypeScript is compiled into regular JavaScript"* - yes, and you'll see the same behaviour in vanilla JS. – jonrsharpe Apr 24 '21 at 12:44
  • I expect the field in my class to contain a reference to `IRunner`. Therefore when i change whatever `runner` is, my classes field should reflect the changes I've made to the `runner` variable. – Michael Apr 24 '21 at 12:46
  • I'm not sure how it works in C#, but the reference you refer to is a reference to an actual object: the `FooRunner` instance that was passed in to the constructor. It's not a reference to the *variable* called `runner`, so won't magically change when that variable happens to be assigned a new value. I'd be surprised if any language behaved the way you seem to expect (except perhaps C if you were explicitly manipulating pointers). – Robin Zigmond Apr 24 '21 at 12:46
  • "_Coming from C#, where objects are passed by reference_". Only when you pass them using the `ref` or `out` keywords. Otherwise, also in C#, they are passed by value. – Ivar Apr 24 '21 at 12:46
  • @Ivar, that's not true. You can pass an object without ref and the changes done on that object will be reflected everywhere else, where you're referencing to that object. Please read the following documentation https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref – Michael Apr 24 '21 at 12:49
  • 2
    @lilbroomstick you mean if you pass a mutable object by value? You'll see the same behaviour in JS. – jonrsharpe Apr 24 '21 at 12:50
  • @jonrsharpe, here: https://dotnetfiddle.net/rqlr6k – Michael Apr 24 '21 at 13:01
  • 1
    [The same applies to JS](https://jsfiddle.net/1305tax2/) @lilbroomstick. – Ivar Apr 24 '21 at 13:05
  • 1
    @Ivar, yup! My bad! It's become much clearer when equivalent C# code appeared in the answers. – Michael Apr 24 '21 at 13:11
  • 1
    "Coming from C#, where objects are passed by reference" – Huh? C# is pass-by-value by default, *unless* you *explicitly* ask for pass-by-reference using either `ref` or `out`. It is clearly documented here: https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/ref – Jörg W Mittag Apr 24 '21 at 13:28
  • @JörgWMittag, yeah... I guess I'm not thinking about this right.. found this really helpful https://stackoverflow.com/questions/9996359/passing-a-class-as-a-ref-parameter-in-c-sharp-does-not-always-work-as-expected. So, we're passing a _copy of a reference_ to the object. When using ref we're affecting the original variable as well. – Michael Apr 24 '21 at 19:11
  • Thank you so much for helping. – Michael Apr 24 '21 at 19:12
  • Turns out I'm confusing _reference/value types_ with _passing by reference/value_. These are two different concepts. Am I correct? – Michael Apr 24 '21 at 19:24

2 Answers2

3

In C# the code I've written would behave as I expect it to. But here it's not.

Not really... If you transform your code above into C# code and run it, you will see that it behaves exactly as your TypeScript code.

The reason is that you pass runner to the constructor of TestDI. What happens here... The content is simply copied for the call of the constructor and in this case that means that the address of the specified object is copied. If you now assign a new address of another object to runner, why should this be reflected in the already done copy within your TestDI instance? I guess you have mixed up something in your brain a little bit ;-)

Coming from C#, where objects are passed by reference (if they're not primitive types or structs). I've written some TypeScript code to test something:

Well yes, you CAN pass them as reference (by using ref for example) but by default the address is simply copied (as explained above).

To make it easier for you: Just copy & paste and be suprised

class Program
{
    static void Main(string[] args)
    {
        IRunner runner;
        runner = new FooRunner();

        TestDI SUT = new TestDI(runner);

        // Expected output: Foo
        // Actual output: Foo
        Console.WriteLine(runner.Run());

        // Expected output: Foo
        // Actual output: Foo
        SUT.Test();

        runner = new BarRunner();

        // Expected output: Bar
        // Actual output: Bar
        Console.WriteLine(runner.Run());

        // Expected output: Bar
        // Actual output: Foo
        SUT.Test();
    }
}


public interface IRunner
{
    string Run();
}

public class TestDI
{
    private readonly IRunner runner;

    public TestDI(IRunner runner)
    {
        this.runner = runner;
    }

    public void Test()
    {
        Console.WriteLine("Result of runner: " + this.runner.Run());
    }
}

public class FooRunner : IRunner
{
    public string Run()
    {
        return "Foo";
    }
}

public class BarRunner : IRunner
{
    public string Run()
    {
        return "Bar";
    }
}
Markus Safar
  • 6,324
  • 5
  • 28
  • 44
  • Whoa! I deeply apologise for my question! I didn't notice that I actually set `runner` to something new! And still, reference to the previous object `FooRunner` is kept in my class. Everything works as it should! – Michael Apr 24 '21 at 12:58
  • 1
    @lilbroomstick Nothing to apologize for ;-) – Markus Safar Apr 24 '21 at 13:01
1

Trying to keep it as simple and clear as possible: you're passing the reference of the object new FooRunner(), not the reference of the variable runner, so assigning a new value to runner doesn't change what you passed to the constructor and SUT.Runner stays the same.

Giovanni Londero
  • 1,309
  • 1
  • 10
  • 20