0

This is kind of a basic question, but I learned programming in C++ and am just transitioning to C#, so my ignorance of the C# methods are getting in my way.

A client has given me a few fixed length files and they want the 484th character of every odd numbered record, skipping the first one (3, 5, 7, etc...) changed from a space to a 0. In my mind, I should be able to do something like the below:

static void Main(string[] args)
{
    List<string> allLines = System.IO.File.ReadAllLines(@"C:\...").ToList();

    foreach(string line in allLines)
    {
        //odd numbered logic here
        line[483] = '0';
    }
...
//write to new file
}

However, the property or indexer cannot be assigned to because it is read only. All my reading says that I have not set a setter for the variable, and I have tried what was shown at this SO article, but I am doing something wrong every time. Should what is shown in that article work? Should I do something else?

Jeff.Clark
  • 599
  • 1
  • 5
  • 27

4 Answers4

1

You cannot modify C# strings directly, because they are immutable. You can convert strings to char[], modify it, then make a string again, and write it to file:

File.WriteAllLines(
    @"c:\newfile.txt"
,   File.ReadAllLines(@"C:\...").Select((s, index) => {
        if (index % 2 = 0) {
            return s; // Even strings do not change
        }
        var chars = s.ToCharArray();
        chars[483] = '0';
        return new string(chars);
    })
);
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Holy cow, that works. Now to figure out what in the world is actually happening in that code :) I am going to wait to accept this answer to see if Rufus's or Lazydeveloper's answer works, and figure out the pro's/con's of each. – Jeff.Clark Jan 30 '18 at 03:42
  • @Jeff.Clark Since you are coming from C++ background, the idea is similar to using `std::transform` with lambda expressions, such as [in this example](https://stackoverflow.com/a/3885392/335858). `s => { ... }` is a lambda that makes the conversion of a single line. – Sergey Kalinichenko Jan 30 '18 at 04:14
  • Just FYI, I had a requirement change to make it every odd numbered record starting with record #3. I am playing around with this now to try see if I can get it, but I have updated the question and you can update your answer if you wish. – Jeff.Clark Jan 30 '18 at 14:18
  • @Jeff.Clark That's a straightforward change - please see the edit. – Sergey Kalinichenko Jan 30 '18 at 14:27
  • I changed it to `(index % 2 == 1 || index == 0)` so the first record is skipped, and all even records are skipped. <3 off by one errors :) – Jeff.Clark Jan 30 '18 at 18:14
1

Since strings are immutable, you can't modify a single character by treating it as a char[] and then modify a character at a specific index. However, you can "modify" it by assigning it to a new string.

We can use the Substring() method to return any part of the original string. Combining this with some concatenation, we can take the first part of the string (up to the character you want to replace), add the new character, and then add the rest of the original string.

Also, since we can't directly modify the items in a collection being iterated over in a foreach loop, we can switch your loop to a for loop instead. Now we can access each line by index, and can modify them on the fly:

for(int i = 0; i < allLines.Length; i++)
{
    if (allLines[i].Length > 483)
    {
        allLines[i] = allLines[i].Substring(0, 483) + "0" + allLines[i].Substring(484);
    }
}

It's possible that, depending on how many lines you're processing and how many in-line concatenations you end up doing, there is some chance that using a StringBuilder instead of concatenation will perform better. Here is an alternate way to do this using a StringBuilder. I'll leave the perf measuring to you...

var sb = new StringBuilder();
for (int i = 0; i < allLines.Length; i++)
{
    if (allLines[i].Length > 483)
    {
        sb.Clear();
        sb.Append(allLines[i].Substring(0, 483));
        sb.Append("0");
        sb.Append(allLines[i].Substring(484));

        allLines[i] = sb.ToString();
    }
}
Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • Just FYI, I had a requirement change to make it every odd numbered record starting with record #3. I am playing around with this now to try see if I can get it, but I have updated the question and you can update your answer if you wish – Jeff.Clark Jan 30 '18 at 14:19
  • You mean only apply this to every other line, staring with the third one (which is index 2)? Like, for example: `for (int i = 2; i < allLines.Length; i+=2)`? – Rufus L Jan 30 '18 at 14:38
-1

The first item after the foreach (string line in this case) is a local variable that has no scope outside the loop - that’s why you can’t assign a value to it. Try using a regular for loop instead.

Michael Tracy
  • 210
  • 3
  • 9
  • 1
    While that is true, even in a `for` loop you still can't change a specific character in a string by assigning a new value to that index. As a char array, strings are read-only. – Rufus L Jan 30 '18 at 03:36
-1

Purpose of for each is meant to iterate over a container. It's read only in nature. You should use regular for loop. It will work.

 static void Main(string[] args)
    {
        List<string> allLines = System.IO.File.ReadAllLines(@"C:\...").ToList();

        for (int i=0;i<=allLines.Length;++i)
        {
           if (allLines[i].Length > 483)
    {
        allLines[i] = allLines[i].Substring(0, 483) + "0";
    }
        }
    ...
    //write to new file
    }
lazydeveloper
  • 891
  • 10
  • 20