4

I might get some downvotes here, but I've actually found conflicting information through normal searches and would like a definitive answer other people can also easily find.

Given a property in current C#:

public static IEnumerable<string> foo { get; set; } = new string[] { "bar", "bar2" };

We know that the default value of foo will return as the above array. If another value is assigned, the default value is no longer used. However, what if the case is :

public static IEnumerable<string> foo { get; set; } = GetMyStrings();

My thought is that this functions like:

if(foo == null) { foo = GetMyStrings();}

and that foo will retain the value from its first run of GetMyStrings() for the lifetime of the object unless overwritten by a manually assigned value.

My colleague insists this risks a GC problem, and that the result of GetMyStrings() can fall out of scope and be collected, "renullifying" the parameter and causing multiple calls to GetMyStrings() over the lifetime of the object.

Which of us is correct?

CDove
  • 1,940
  • 10
  • 19

3 Answers3

5

No, actually it is like this:

static ClassName()
{
    foo = GetMyStrings();
}

The compiler generates a static constructor and puts the assignment call in there (just like it creates a line in the constructor for non-static properties).

My colleague insists this risks a GC problem, and that the result of GetMyStrings() can fall out of scope and be collected, "renullifying" the parameter and causing multiple calls to GetMyStrings() over the lifetime of the object.

He is talking nonsense. An assigned instance will never be garbage collected, no matter from where exactly it was assigned.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Which is enough right? The 'static' instance will never get dropped and so the assigned static members won't. – Patrick Hofman Apr 19 '17 at 12:18
  • Thanks. The same colleague once told me he doesn't use Linq because it's "not been around long enough yet". 3 years ago. I get lunch for free today. – CDove Apr 19 '17 at 12:19
  • LINQ was released with C# 3, which was in 2007. It is 10 years old already. For most technology already at the end of their lifetime ;) @CDove – Patrick Hofman Apr 19 '17 at 12:20
  • @CDove whaaaaattt!?! <3 LINQ, has changed my programming style completely when I started using it a few months ago – Blake Thingstad Apr 19 '17 at 12:20
  • Ever had one of those coworkers who swore anything that is newer than the point at which they best understood their craft is automatically the devil's magic? I win a lot of bets... – CDove Apr 19 '17 at 12:27
  • You know the place to win a bet – VMAtm Apr 19 '17 at 16:02
0

Writing this

public static IEnumerable<string> Foo { get; set; } = GetMyStrings();

is syntactic sugar over this declaration

public static IEnumerable<string> Foo { get; set; }

and an addition of static constructor that looks like this:

static MyClass() {
    Foo = GetMyStrings();
}

(if you already have a static constructor, Foo = GetMyStrings(); line is added to it implicitly).

Other than that, there is no difference: once it is time for the class to get initialized, GetMyStrings() is called from the constructor, then the value it returns is assigned to Foo. After that the value remains there until it is replaced by another one, or the program ends.

As far as "renullifying" the parameter goes, it never happens to the same instance of the class object. You can get multiple calls to GetMyStrings() when app domain containing the class gets unloaded, or when the class is loaded into another app domain, but that has nothing to do with the new C# 6 syntax.

Finally, if you do not plan to change Foo after the static constructor, consider making it read-only:

public static IEnumerable<string> Foo { get; } = GetMyStrings();
Community
  • 1
  • 1
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
0

To exemplify my comment, look at this example:

public void RunTest()
{
    Test t = new Stackoverflow.Form1.Test();
    Console.WriteLine(t.Values.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values.First());

    Console.WriteLine("------");
    Console.WriteLine(t.Values2.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values2.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values2.First());

    Console.WriteLine("------");
    Console.WriteLine(t.Values3.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values3.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values3.First());
}

public class Test
{
    public IEnumerable<string> Values { get; set; } = GetValues();

    public static IEnumerable<string> GetValues()
    {
        List<string> results = new List<string>();
        for (int i = 0; i < 10; ++i)
        {
            yield return DateTime.UtcNow.AddMinutes(i).ToString();
        }
    }

    public IEnumerable<string> Values2 { get; set; } = GetValues2();

    public static IEnumerable<string> GetValues2()
    {
        return GetValues().ToList();
    }

    public IEnumerable<string> Values3 { get; set; } = GetValues().ToList();
}

The output from this is:

19/04/2017 12:24:25
19/04/2017 12:24:26
19/04/2017 12:24:27
------
19/04/2017 12:24:25
19/04/2017 12:24:25
19/04/2017 12:24:25
------
19/04/2017 12:24:25
19/04/2017 12:24:25
19/04/2017 12:24:25

Values: As you can see, the IEnumerable<> from the method using yield will be evaluated each time you call it.

Values2: The method returns a List. Since List implements IEmnumerable, you're still just returning the concrete instance of List (albeit presented as its IEnumerable interface) and thus it only gets evaluated once.

Values3: We've basically moved the .ToList() outside of the GetValues2() method and just implemented it directly on the property. The result here is the same as Values2, for basically the same reason.

When you return a concrete object that implements IEnumerable, it will be evaluated once, but watch out for the first example.

And remember folks, Linq is a view. if the underlying data changes, so does what's returned from your Linq expression.


Re the garbage collection question: Objects that are referenced by an active object will not be garbage collected.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86