【问题标题】:Local variable inside recursive function unassigned递归函数内的局部变量未赋值
【发布时间】:2010-02-15 01:07:59
【问题描述】:

我有以下代码,关于如何解决这个问题的任何想法,而不是在函数外部声明一个 int 变量?我收到以下编译器错误:Use of unassigned local variable 'counter'

public static int GetNumberOfDevicesForManagementGroup(Guid managementGroupId, bool firstTime)
  {
     int counter;
     using (var ctx = new DeviceManagerEntities())
     {
        if (firstTime)
        {
           firstTime = false;
           counter = 0;
           GetNumberOfDevicesForManagementGroup(managementGroupId, firstTime);
        }
        else
        {
           var groups = ctx.ManagementGroups
              .Where(x => x.ParentId == managementGroupId)
              .ToList();
           if (groups.Count != 0)
           {
              foreach (ManagementGroups group in groups)
              {
                 var devices = ctx.Devices
                    .Where(x => x.ManagementGroups.ManagementGroupId == group.ManagementGroupId)
                    .ToList();
                 foreach (Devices device in devices)
                 {
                    counter++;
                 }
                 GetNumberOfDevicesForManagementGroup(group.ManagementGroupId, firstTime);
              }
           }
           else
           {
              var devices = ctx.Devices
                    .Where(x => x.ManagementGroups.ManagementGroupId == managementGroupId)
                    .ToList();
              foreach (Devices device in devices)
              {
                 counter++;
              }
           }
        }
     }
     return counter;
  }

【问题讨论】:

  • 为什么不能在函数外声明?
  • 我可以,只是想知道是否还有其他方法?

标签: c# recursion


【解决方案1】:

这个函数似乎有很多地方出了问题。

  1. 你有一个递归函数,它创建一个新的实体上下文 - 并在上下文被释放之前递归!因此,这不仅会创建大量冗余的ObjectContext 实例,而且它们都同时使用。整个事情应该完全重写,以便在函数调用之间共享上下文。

  2. 您在静态方法中创建ObjectContext。这真是糟糕的设计。特别是考虑到此方法的名称,您似乎在滥用静态方法来实现有效的过程代码。这应该是一个实例方法,而类应该是真正维护ObjectContext的东西。

  3. 你有一堆这样的行:GetNumberOfDevicesForManagementGroup(managementGroupId, firstTime);。除了浪费 CPU 周期和数据库时间之外,它们什么都不做。你正在丢弃你从他们那里得到的结果。看起来您认为GetNumberOfDevicesForManagementGroup 的连续执行将共享相同的counter 变量;这不是递归的工作方式,这不是子例程的工作方式,并且将counter 设为全局变量来补偿是错错错

  4. 您无需在每个实例中实际获取 计数,您只需下载所有“设备”并逐个计数即可。这又是对 CPU 和数据库时间的巨大浪费。

  5. 您正在循环运行数据库查询。哎呀。

  6. 第一个 if 块中的两行 firstTime = false;counter = 0; 根本不做任何事情。您正在分配给函数参数。这些都是空操作。

  7. 您实际上从未为else 块初始化counter,因此编译器错误确实不足为奇。如果你想增加一个变量,比如counter++,它必须从某个地方开始。

老实说,这看起来像是一些粗鲁的程序代码,被随意“转换”为 C#。您需要完全重写此方法。您可能需要重做很多设计。


这是一个重写类的示例,如果我正确理解了您的代码,它将完成相同的任务(获取单个管理组及其子树中所有管理组的设备计数):

public class DeviceRepository
{
    private DeviceManagerEntities context;

    public DeviceRepository(DeviceManagerEntities context)
    {
        if (context == null)
            throw new ArgumentNullException("context");
        this.context = context;
    }

    public int GetDeviceCount(Guid managementGroupID)
    {
        return GetDeviceCount(new Guid[] { managementGroupID });
    }

    public int GetDeviceCount(IEnumerable<Guid> managementGroupIDs)
    {
        int deviceCount = context.Devices
            .Where(d => managementGroupIDs.Contains(
                d.ManagementGroups.ManagementGroupID))
            .Count();
        var childGroupIDs = context.ManagementGroups
            .Where(g => managementGroupIDs.Contains(g.ParentId))
            .Select(g => g.ManagementGroupID);
        deviceCount += GetDeviceCount(childGroupIDs);
        return deviceCount;
    }
}

请注意,这仍然不会很好地执行,因为它会通过对每个子组的新查询来锤击数据库;为了解决这个问题,您需要在数据库本身中实现递归查询。

【讨论】:

  • 大部分都同意。但是,你怎么知道是ObjectContext?你怎么知道这是一个数据库查询?
  • @Martinho:我知道是因为这条线:var ctx = new DeviceManagerEntities()
  • 我还是没跟上。我看不到任何地方的定义。据我所知,它可能是class DeviceManagerEntities : List&lt;DeviceManager&gt;。我确实同意,如果这真的是一个数据库的东西,代码太可怕了。
  • @Martinho:该类同时具有DevicesManagementGroups 属性,因此它绝对不是List&lt;DeviceManager&gt;var 混淆了真实类型,但我向你保证它是一个 EF 容器。即使这不是数据访问代码,它仍然必须重写。 ;)
【解决方案2】:

您似乎误解了递归函数的工作原理:您不返回结果(即使函数末尾有一个return!)。您似乎认为 counter 在递归调用之间共享 - 但情况恰恰相反。事实上,递归的原理就是基于共享发生的事实。

每个递归调用都会获得一个 counter 变量。将所有这些结果加在一起是您的工作。例如,仅使用递归锚点:

if (firstTime)
{
    firstTime = false;
    counter = 0;
    GetNumberOfDevicesForManagementGroup(managementGroupId, firstTime);
}

这是错误的;它应该看起来像这样:

if (firstTime)
{
    return GetNumberOfDevicesForManagementGroup(managementGroupId, false);
}

这里重要的是返回结果。但是设置firstTime 是不必要的(也是不寻常的),设置counter 也是不必要的。

方法体的其余部分必须相应更改。

(另外,这个递归锚似乎没有意义,也可以省略。)

【讨论】:

    【解决方案3】:

    这样写:

    您可以插入您的计数器值作为参数。

    public static int GetNumberOfDevicesForManagementGroup(Guid managementGroupId, bool firstTime, int counterValue)
      {
         int counter = 0;
         counter = counterValue;
    
         using (var ctx = new DeviceManagerEntities())
         {
            if (firstTime)
            {
               firstTime = false;
               counter = 0;
               GetNumberOfDevicesForManagementGroup(managementGroupId, firstTime);
            }
            else
            {
               var groups = ctx.ManagementGroups
                  .Where(x => x.ParentId == managementGroupId)
                  .ToList();
               if (groups.Count != 0)
               {
                  foreach (ManagementGroups group in groups)
                  {
                     var devices = ctx.Devices
                        .Where(x => x.ManagementGroups.ManagementGroupId == group.ManagementGroupId)
                        .ToList();
                     foreach (Devices device in devices)
                     {
                        counter++;
                     }
                     GetNumberOfDevicesForManagementGroup(group.ManagementGroupId, firstTime);
                  }
               }
               else
               {
                  var devices = ctx.Devices
                        .Where(x => x.ManagementGroups.ManagementGroupId == managementGroupId)
                        .ToList();
                  foreach (Devices device in devices)
                  {
                     counter++;
                  }
               }
            }
         }
         return counter;
      }
    

    您可以插入您的计数器值作为参数。

    【讨论】:

    • 抱歉,这行不通。将完全忽略递归结果。
    【解决方案4】:

    counter 初始化为零以使编译器静音!事实上 为什么不在函数之外让它成为静态的呢?

    静态 int 计数器 = 0; 公共静态 int GetNumberOfDevicesForManagementGroup(Guid managementGroupId, bool firstTime) { …… }

    编辑:似乎OP希望在函数范围内有一个变量,另一种方法是修改函数的签名以包含变量@的参数987654322@ 并将其改为 out 参数...

    希望这会有所帮助, 此致, 汤姆。

    【讨论】:

    • 如果他想在函数的每次递归之间保存计数器状态怎么办?然后,如果您在函数内部将其设置为零,则每次它都会变为零。
    • @Tony:修改了答案! :)
    • 这就是我的意思,你不能在函数内部将它设置为 0,这会破坏递归函数背后的整个想法。
    • fARcRY:当函数被调用时,计数器在堆栈范围内……除非你修改函数,否则使用 out 参数作为计数器!无论哪种方式,您都无法获胜...
    • @Martinho:好的,想象一下如果函数签名中有一个用于counter的out变量,没问题,如果你想调用两次函数,重新初始化counter(在静态全局范围的上下文中) 在再次调用它之前。或者你的意思是在递归上下文中......恕我直言,OP想要他的蛋糕并吃掉它,一个无法初始化但想要使用计数器变量的递归函数......类似于catch 22情况......
    【解决方案5】:

    正如其他人所建议的那样,如果您在声明时将 counter 初始化为零,您就可以解决编译问题。但只有那一个。

    但是...为什么使用firstTime 参数作为累加器?为什么不将其完全移除,完全不使用蓄能器?

    public static int GetNumberOfDevicesForManagementGroup(Guid managementGroupId)
    {
         // *** Initialization
         int counter = 0;
         using (/* ... */)
         {
           // *** No first time special case
           var groups = // ...
           if (groups.Count != 0)
           {
              foreach (ManagementGroups group in groups)
              {
                 // *** No need to call ToList() to count
                 counter += ctx.Devices
                    .Count(x => x.ManagementGroups.ManagementGroupId == group.ManagementGroupId)
                 // *** Add recursive result
                 counter += GetNumberOfDevicesForManagementGroup(group.ManagementGroupId);
              }
           }
           else
           {
              // *** Use LINQ to count
              counter = devices.Count(x => x.ManagementGroups.ManagementGroupId == group.ManagementGroupId);
           }
         }
         return counter;
      }
    

    【讨论】:

      猜你喜欢
      • 2020-11-28
      • 1970-01-01
      • 1970-01-01
      • 2011-05-05
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多