在 C# 中使用“out 参数”递归

本文关键字:out 参数 递归 | 更新日期: 2023-09-27 18:32:06

我可以将out参数与递归方法一起使用吗?如果可能,如何使用以下代码执行此操作?

private void PrepareDir(out List<_File> listFiles,string root,string dirPath) {
    DirectoryInfo dirRoot = new DirectoryInfo(dirPath);
    FileInfo [] Files = dirRoot.GetFiles();
    dirPath = dirPath.Substring(root.Length);
    foreach (FileInfo file in Files) {
        _File _file = new _File();
        _file.Name = dirPath + "''" + file.Name;
        _file.Path = file.FullName;
        _file.Size = file.Length;
        listFiles.Add(_file);
    }
    foreach (DirectoryInfo dir in dirRoot.GetDirectories()) {
        PrepareDir(out listFiles, root, dir.FullName);
    }
}
private void btnButton1_Click(object sender, EventArgs e) {
    List<_File> Files = new List<_File>();
    PrepareDir(out Files,currAddress, currAddress);
}

在 C# 中使用“out 参数”递归

我决定重写我的答案,以更直接地说明为什么这里不需要使用out。我写这篇文章相当冗长,因为我认为你问题的根源更多的是不理解按值传递、按引用传递以及引用类型通过引用传递之间的常见混淆之间的区别。另请注意,这并非仅对递归明确。

在 C# 中,默认情况下按值传递引用类型。很多人可能会将引用类型与按引用传递混淆,但有一个重要的区别。为了使自己更清楚,我将按原样引用引用类型,并使用 refout 关键字通过引用传递。

按值传递引用类型(因为它是对内存中某个存储位置的引用)允许您进行和保留更改。以这个例子为例。

public class MyRefType
{
    public int Value { get; set; }
}
public void Foo() {
    MyRefType type = new MyRefType();
    AddOne(type);
}
public void AddOne(MyRefType type) {
    type.Value++;
}

这里将发生的情况是,类type现在的值将为 1。这就是引用类型的本质。如果type是一个struct,它在Foo方法中仍然具有0的值,因为制作了一个副本而不是保存对对象的引用。

现在我希望您了解按值传递引用类型的语义,让我们实际讨论如何通过引用传递引用类型。这是使用 outref 关键字完成的。立即注意,在 CLR 中,out技术上不存在,只有ref存在。 out实际上在元数据中表示为ref,并应用了特殊属性,应将它们视为相同。

现在,假设我们想在执行添加之前在 AddOne 方法中重新分配引用类型。

public class MyRefType
{
    public int Value { get; set; }
}
public void Foo() {
    MyRefType type = new MyRefType();
    AddOne(type);
}
public void AddOne(MyRefType type) {
    type = new MyReferenceType();
    type.Value++;
}

由于我们仍在按值传递引用类型,因此方法Foo中的 type 值仍将为 0。我们所做的只是初始化内存中的另一个存储位置,而不是重新分配原始存储位置)。但是我们希望通过引用实际传递我们的引用类型,所以我们真的应该这样做:

public class MyRefType
{
    public int Value { get; set; }
}
public void Foo() {
    MyRefType type = new MyRefType();
    AddOne(ref type);
}
public void AddOne(ref MyRefType type) {
    type = new MyReferenceType();
    type.Value++;
}

现在,Foo方法中type的值将为 1。这是因为我们重用了引用指向的相同存储位置,而不是在内存中创建新位置。

那么这一切如何适用于out呢?请记住,out与具有不同用法的对称ref相同。不同之处在于,使用 ref 时,您可以保留该方法而不初始化引用,而使用 out 时,必须在方法返回之前显式初始化它。

因此,在您在此处使用 out 的情况下,完全没有必要,因为您已经有现有的引用语义为您工作。我希望这能澄清一点。

您在代码示例中滥用了关键字 out。您的List<_File>作为引用类型传递,对该列表所做的任何更改都将影响调用方的列表。仅当要重新分配调用方的标识符时,才需要传递 out 参数。

考虑:

public bool Foo()
{
  List<int> list = new List<int>();
  list.Add(1);
  Bar(out list);
  return list.Contains(1);
}
public void Bar(out List<int> list)
{
  list = new List<int>();
  list.Add(2);
}

Foo会返回假。

另一方面,如果Bar是这样的:

public void Bar(List<int> list)
{
  list = new List<int>();
  list.Add(2);
}

然后Foo会返回 true。有关更多详细信息,请参阅out

您的代码将在不使用 out 的情况下按原样工作。而且,事实上,不会编译 - 并且使其编译会增加您尝试执行的操作的不必要的复杂性。不要在您的情况下使用out