MadBoy
MadBoy

Reputation: 11104

Using using and getting variable assigned inside using in C#

I always thought that by declaring var before the using will allow it to be assigned inside using and then I could still read variable outside of it. Turns out I can't :-)

ReadOnlyCollection<string> collection;
using (var archive = new SevenZipArchive(varRarFileName)) {
    collection = archive.Volumes;
    MessageBox.Show(collection.Count.ToString());  // Output 10
}
MessageBox.Show(collection.Count.ToString()); // output 0

Any way to make it work without stopping to use using

Full testing method:

private ReadOnlyCollection<string> ExtractRar(string varRarFileName, string varDestinationDirectory) {
    ReadOnlyCollection<string> collection;
    using (var archive = new SevenZipArchive(varRarFileName)) {
        collection = new ReadOnlyCollection<string>(archive.Volumes); 
        MessageBox.Show(collection.Count.ToString()); // output 10
    }
    MessageBox.Show(collection.Count.ToString()); // output 0
    return collection;
}

Upvotes: 3

Views: 2157

Answers (5)

phillip
phillip

Reputation: 2738

The problem is that the field you are referencing is part of archive. The archive object doesn't exist at that point because of the using closure.

You could clone the values inside the using which would give you a copy of the list instead of a reference to the list values and that would do the job.

Upvotes: 0

Luke Hutton
Luke Hutton

Reputation: 10722

I tried something similar and I get a passing test:

[Test] public void CollectionCountShouldBeGreaterThanZero() {
  // arrange
  string tempDir = Path.GetTempPath();
  var fileInfo = new FileInfo(tempDir + Path.DirectorySeparatorChar + "test.zip");
  File.WriteAllBytes(fileInfo.FullName, Resources.TestZipFile);

  SevenZipBase.SetLibraryPath(@"c:\7z.dll");

  // act
  ReadOnlyCollection<string> collection;
  using(var archive = new SevenZipExtractor(fileInfo.FullName))
    collection = archive.ArchiveFileNames;

  // assert
  Assert.IsTrue(collection.Count > 0);
}

Upvotes: 1

Lee
Lee

Reputation: 144136

As Joel Rondeau points out in his answer, the collection is being cleared as the archive is being disposed of. However, wrapping it in a ReadonlyCollection won't work as this does not copy the wrapped list. You need to create this copy manually:

ReadOnlyCollection<string> collection;
using (var archive = new SevenZipArchive(varRarFileName))
{
    collection = new ReadOnlyCollection<string>(archive.Volumes.ToList());
}

Upvotes: 7

Jon Skeet
Jon Skeet

Reputation: 1500785

You can definitely still read from the variable. There's no problem in terms of definite assignment, or you'd get a compile-time error. For example, this is fine:

using System;
using System.IO;

class Test
{
    static void Main()
    {
        string x;
        using (new MemoryStream())
        {
            x = "hello";
        }
        Console.WriteLine(x);
    }
}

That's absolutely fine.

Now if SevenZipArchive returns a ReadOnlyCollection<string>, I'd usually expect that to still be valid after the archive itself is disposed. However, ReadOnlyCollection<T> is simply a wrapper around another collection... and if that collection is being invalidated by disposing of archive, that would certainly explain things.

Unfortunately, Joel's suggested way of copying the collection is only creating another wrapper - which will ask the first wrapper for the count, in turn asking the original (invalidated) collection.

Here's one approach which should work:

private ReadOnlyCollection<string> ExtractRar(string varRarFileName,
                                              string varDestinationDirectory) {
    ReadOnlyCollection<string> collection;
    using (var archive = new SevenZipArchive(varRarFileName)) {
        collection = new ReadOnlyCollection<string>(archive.Volumes.ToList()); 
        MessageBox.Show(collection.Count.ToString()); // output 10
    }
    MessageBox.Show(collection.Count.ToString()); // output 0
    return collection;
}

Note the extra call to ToList(). That will force the collection to be copied to a List<string> first... truly copied, not just creating a wrapper.

Of course, if you don't really mind if the method returns a List, you could just use:

private List<string> ExtractRar(string varRarFileName,
                                string varDestinationDirectory) {
    List<string> collection;
    using (var archive = new SevenZipArchive(varRarFileName)) {
        collection = archive.Volumes.ToList(); 
        MessageBox.Show(collection.Count.ToString()); // output 10
    }
    MessageBox.Show(collection.Count.ToString()); // output 0
    return collection;
}

... and then when you don't need the extra diagnostics:

private List<string> ExtractRar(string varRarFileName,
                                string varDestinationDirectory) {
    using (var archive = new SevenZipArchive(varRarFileName)) {
        return archive.Volumes.ToList(); 
    }
}

(I'm assuming you're using .NET 3.5 or higher, by the way, to use the ToList extension method.)

Upvotes: 2

Joel Rondeau
Joel Rondeau

Reputation: 7586

Copy archive.Volumes instead of just having collection reference it. Then when archive is disposed at the end of the using, your collection won't have been disposed.

Upvotes: 6

Related Questions