-1

Possible Duplicate:
Why do I have to assign a value to an int in C# when defaults to 0?

I'm just starting to learn C# by writing a contrived application called Journal. In a function for parsing journal files I've declared the variable DateTime currentEntryDate. It won't get a value until I reach a line that defines a new entry. The second time I reach an entry line, the variable will be used to create an instance of class JournalEntry for the previous entry.

The problem is that the code for the usage of the variable won't compile:

Use of unassigned local variable 'currentEntryDate'

This makes no sense to me. Do I really have to give a wasted initial value to my variables just to keep the compiler happy? Surely I've misunderstood something, or there's an error in my code somewhere.

The code on Pastebin: Journal.cs. I've highlighted the relevant lines.

The code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;

namespace Journal
{
    class Journal
    {
        public List<JournalEntry> Entries;

        private static readonly string EntryLineRegex =
            @"-- Entry: (?<title>.*) \((?<year>\d{4})-(?<month>\d{2})" +
            @"-(?<day>\d{2})\)";

        public static Journal FromFile(string filePath)
        {
            Journal returnValue = new Journal();

            StreamReader fileReader = new StreamReader(filePath);

            // Prepare variables for parsing the journal file.
            bool hitFirstEntry = false;
            DateTime currentEntryDate;
            string currentEntryTitle;
            StringBuilder currentEntryText = new StringBuilder();

            // Prepare a regular expression for the entry lines.
            Regex entryLineRegex = new Regex(EntryLineRegex);

            while (!fileReader.EndOfStream)
            {
                string line = fileReader.ReadLine();

                if (line.StartsWith("--"))
                {
                    // Is this the first entry encountered? If so, don't try to
                    // process the previous entry.
                    if (!hitFirstEntry)
                    {
                        hitFirstEntry = true;
                    }
                    else
                    {
                        // Create a JournalEntry with the current entry, then
                        // reset for the next entry.
                        returnValue.Entries.Add(
                            new JournalEntry(
                                currentEntryText.ToString(), currentEntryDate
                            )
                        );

                        currentEntryDate = new DateTime();
                        currentEntryText.Clear();
                    }

                    // Extract the new entry title and date from this line and
                    // save them.
                    Match entryMatch = entryLineRegex.Match(line);
                    GroupCollection matches = entryMatch.Groups;

                    currentEntryDate = new DateTime(
                        Convert.ToInt16(matches["year"].Value),
                        Convert.ToInt16(matches["month"].Value),
                        Convert.ToInt16(matches["day"].Value)
                    );

                    currentEntryTitle = matches["title"].Value;
                }
                else
                {
                    currentEntryText.Append(line);
                }
            }

            return returnValue;
        }
    }

    class JournalEntry
    {
        public string Text;
        public DateTime EntryDate;

        public JournalEntry(string text, DateTime entryDate)
        {
            this.Text = text;
            this.EntryDate = entryDate;
        }
    }
}
Community
  • 1
  • 1
Hubro
  • 56,214
  • 69
  • 228
  • 381
  • 3
    No. But valid code *must* assign all local variables a value before they are accessed and the compiler *must* be guaranteed of this. I am certain this is a duplicate. –  Jan 20 '13 at 08:45
  • @pst: And how do I guarantee the compiler of that? – Hubro Jan 20 '13 at 08:47
  • It's the compiler's way of ensuring that you don't use variables before they have a value. The compiler can't determine from your complicated conditional statements that `currentEntryDate` will have a value before you use it, so it throws up that error. Is giving `currentEntryDate` an initial value here such a tragedy? – JLRishe Jan 20 '13 at 08:53
  • related http://stackoverflow.com/questions/5710485/c-sharp-use-of-unassigned-local-variable – Vamsi Jan 20 '13 at 08:53
  • @JLRishe: No, it's not a tragedy here, but it seems to me like a terrible solution and I'm sure there are plenty of cases where instantiating a class just as a place holder is very wasteful. – Hubro Jan 20 '13 at 08:54
  • @Codemonkey: as `currentEntryDate` is a `DateTime`, and `DateTime` is a `struct`, no instantiation or memory allocation need be done. And the JIT will most likely elide the initialization anyway. – Daniel Pryden Jan 20 '13 at 08:57
  • @DanielPryden: That makes sense, but what if DateTime was a really fat ugly class? – Hubro Jan 20 '13 at 09:03
  • Just a side note, having this in your loop is just as wasteful and unnecessary as giving `currentEntryDate` an initial value: `currentEntryDate = new DateTime();` since it's assigned a new value immediately after that. – JLRishe Jan 20 '13 at 09:04
  • @JLRishe: You're right, I didn't notice I had written that. – Hubro Jan 20 '13 at 09:05
  • Another closely related question, although not an exact dupe: http://stackoverflow.com/questions/14419175/about-unassigned-variables – Daniel Pryden Jan 20 '13 at 09:09
  • 1
    @pst: Why did you vote this question as a duplicate of [Why do I have to assign a value to an int in C# when defaults to 0](http://stackoverflow.com/q/1423437/388916)? This is a completely different question and deserves a different answer. – Hubro Jan 20 '13 at 09:16
  • @Codemonkey It was a mistake. However, the accepted answer contains the reason ("local variables .. *must* have "definite assignment" before they are used") and there are many other better really duplicate questions (see other links). –  Jan 20 '13 at 22:19

5 Answers5

2

I think the concern here is the compiler is not smart enough to grasp your way of reading the input, and that there is a path of execution for which the variable will NOT be initialized, i.e if it goes through the else first, before the if. To avoid this you might need to initialize as you define it.

Karthik T
  • 31,456
  • 5
  • 68
  • 87
  • So in other words yes, I do have to give the variable an initial value that will never be used just to keep the compiler happy? There is no other way to solve the problem? – Hubro Jan 20 '13 at 08:51
  • 1
    @Codemonkey: no, you just must ensure that no code path reads from an uninitialized variable. Throwing an exception would also satisfy the compiler. – Daniel Pryden Jan 20 '13 at 08:54
  • @DanielPryden It can only go through the inner-most `else` block (where the read of `currentEntryDate` happens) if the `bool` `hitFirstEntry` has changed its value. But it can only change its value in the inner-most `if` block just above that. It is the "entanglement" of the **two** local variables `hitFirstEntry` and `currentEntryDate` that the compiler doesn't consider. This does not depend on the contents of the file. See my answer. – Jeppe Stig Nielsen Jan 20 '13 at 09:14
2

How about restructuring your loop like this? This will ensure that currentEntryDate has a value before you use it:

string line = fileReader.ReadLine();
while (line != null)
{
    // Extract the new entry title and date from this line and
    // save them.
    Match entryMatch = entryLineRegex.Match(line);
    GroupCollection matches = entryMatch.Groups;

    currentEntryDate = new DateTime(
        Convert.ToInt16(matches["year"].Value),
        Convert.ToInt16(matches["month"].Value),
        Convert.ToInt16(matches["day"].Value)
    );

    currentEntryTitle = matches["title"].Value;

    while ((line = fileReader.ReadLine()) != null && !line.StartsWith("--"))
    {
        currentEntryText.Append(line);
    }

    // Create a JournalEntry with the current entry, then
    // reset for the next entry.
    returnValue.Entries.Add(
        new JournalEntry(
            currentEntryText.ToString(), currentEntryDate
        )
    );

    currentEntryText.Clear();
}
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • But this code instantiates a `new DateTime` for every line in the file... That seems to me kinda even more wasteful than the initial value that I was complaining about :P – Hubro Jan 20 '13 at 09:02
  • 1
    @Codemonkey No it doesn't. It instantiates a `DateTime` for the start of each entry, just like yours. And this approach is quite a bit cleaner and straightforward IMHO. – JLRishe Jan 20 '13 at 09:03
  • @Codemonkey: `DateTime` is a `struct`, so it will be stack-allocated here. And allocation in a generational GC is as fast as stack allocation anyway, so there's nothing to worry about. – Daniel Pryden Jan 20 '13 at 09:04
2

In this case the compiler doesn't realize the "dependence" between hitFirstEntry and currentEntryDate.

Even if you can "prove" that whenever hitFirstEntry is changed to true, then the currentEntryDate will be assigned soon after that, and the currentEntryDate won't be read for the first time until (at the earliest) in next iteration of the loop, the compiler is not that sophisticated. Maybe you can re-write your code.

Edit: Here's a "minimal" version of your code:

        bool isFirstTime = true;
        DateTime localVarToBeAssigned;

        while (true)
        {
            if (isFirstTime)
            {
                isFirstTime = false;
            }
            else
            {
                // this reads the variable
                Console.WriteLine(localVarToBeAssigned);
            }

            // this assigns the variable
            localVarToBeAssigned = DateTime.Now;
        }
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • So would you agree that this is a good time to use `DateTime?`? – Hubro Jan 20 '13 at 09:22
  • @Codemonkey No, I think you should try to re-write your code. If there's a part that should be skipped in the first iteration only, consider moving this part to the end of the loop. Then maybe the criterion to stop looping needs to be moved to the middle of the loop block. – Jeppe Stig Nielsen Jan 20 '13 at 09:48
1

If you really, really don't want to instantiate it:

DateTime? currentEntryDate = null

The question mark makes DateTime nullable, which it usually isn't.

Maciej Stachowski
  • 1,708
  • 10
  • 19
  • 2
    In the code, it is guaranteed to be assigned a meaningful value. The compiler just doesn't quite realize that the program flow won't allow the variable to be used before initialization. – Maciej Stachowski Jan 20 '13 at 08:58
  • 1
    I disagree. There are very few place when I find it is cleaner to use this than to assign on each path appropriately (or perhaps refactor a method). But it "will compile". (If there is an off-path that "the compiler doesn't get correct" then it should likely be a bug - throw an Exception or otherwise react accordingly.) –  Jan 20 '13 at 09:01
  • 1
    I'm not sure about it. Assigning it a meaningful value would hide the error happening when the variable is actually used uninitialized. I'd make it nullable and make an assertion before using its value. – Maciej Stachowski Jan 20 '13 at 09:06
  • If this solution is chosen the `hitFirstEntry` varible should be removed. Then `if (currentEntryDate.HasValue)` should be used to check if it is not the first time a `"--"` line is handled. – Jeppe Stig Nielsen Jan 20 '13 at 16:03
-2

You declare local variable this variables dont have default values and take their memory from stack you should initial local variables befor useing them with change line of this code :

currentEntryDate = new DateTime();

too upper lines your code will be work,

saber safavi
  • 452
  • 3
  • 8