使用枚举的 C# 字典

Posted

技术标签:

【中文标题】使用枚举的 C# 字典【英文标题】:C# Dictionary using enum 【发布时间】:2022-01-23 22:05:44 【问题描述】:

我不明白我在犯什么错误。

我正在计算一些学生的总体平均数,并根据这个平均数为班级提供一个概念。

public enum Grade
    
        A,
        B,
        C,
        D,
        E
    

var overallAverage = fullScore / numberStudents;
Grade generalGrade;

var cases = new Dictionary<Func<decimal,bool>,Grade>
                        
                            x => overallAverage < 2, generalGrade = Grade.E,
                            x => overallAverage < 4, generalGrade = Grade.D,
                            x => overallAverage < 6, generalGrade = Grade.C,
                            x => overallAverage < 8, generalGrade = Grade.B,
                            x => overallAverage < 10, generalGrade = Grade.A
                        ;   
                        
Console.WriteLine($"Overall Average: overallAverage - GRADE: generalGrade");

当我开始学习 C# 时,我决定使用 Dictionary, 但是一般等级的结果并不是预期的。对于任何平均成绩,总成绩返回 A。总体平均成绩正确返回。

【问题讨论】:

我看不到你在哪里使用你的字典。但考虑到你的成绩似乎在 0..10 范围内。所有这些值都小于 10(字典中的最后一个 Func)。此外,使用 Func 作为字典中的键似乎很奇怪。你想做什么?我怀疑一系列级联的 if/else if 语句会更有意义 您正在输出 generalGrade 的值,它将采用您的枚举类型的默认值,因为您从未设置它,这将是第一个定义的条目 - Grade.A @Flydog57 我试图避免使用多个 if else 和 switch case 的替代方案,所以我错误地使用了 Dictionary 使用像Func&lt;...&gt; 这样的委托类型作为字典键类型不是一个好主意。 【参考方案1】:

您用Key 声明字典 lambdaDictionary&lt;Func&lt;decimal, bool&gt;, Grade&gt;,从技术上讲,您可以在 Linq 的帮助下查询它: p>

 using System.Linq;

 ...

 // Techincally, it's possible to use the current dictionary...
 generalGrade = cases
   .Where(pair => pair.Key((decimal)overallAverage))
   .Select(pai => pai.Value)
   .Min();

但是,这不是一个好的做法:请注意,我们查询字典,而不是通过Key 获取Value。如果您想使用字典,请尝试

 // Just mapping average to grade
 var cases = new Dictionary<int, Grade>() 
   0, Grade.E,
   1, Grade.E,
   2, Grade.E,
   3, Grade.D,
   4, Grade.D,
   5, Grade.C,
   6, Grade.C,
   7, Grade.B,
   8, Grade.B,
   9, Grade.A,
  10, Grade.A,
 ;

 // Typical dictionary usage: having key (truncated average)
 // we obtain value - corresponding grade
 generalGrade = cases[(int) overallAverage];

如果是任意边界,我投票给List&lt;&gt;或数组和查询:

 using System.Linq;

 ...

 IReadOnlyList<(grade : Grade, min : double)> cases = 
   new List<(grade : Grade, min : double)>() 
     (Grade.A, 8), // to have A student must get at least 8 average
     (Grade.B, 6), // to have B student must get at least 6 average
     (Grade.C, 4),
     (Grade.D, 2),
     (Grade.E, 0), 
 ;

 ...

 generalGrade = cases
   .Where(item => item.min <= overallAverage)
   .OrderBy(item => item.grade)
   .First()
   .grade; 

【讨论】:

【参考方案2】:

字典建立了键和值之间的关系,在这种情况下,您将一个函数作为键传递,并且为每个函数分配一个等级,这很奇怪,它可能不是您想要的做。

如果您真的想使用字典,那么您可能需要:

var cases = new Dictionary<int, Grade>

    1, generalGrade = Grade.E,
    2, generalGrade = Grade.E,
    3, generalGrade = Grade.D,
    4, generalGrade = Grade.D,
   // ... add the rest of the cases
;   

Grade generalGrade = cases[(int)overallAverage];

但这一点效率都没有,我也用过int,但如果你想用小数,那不是最佳解决方案。

改为尝试:

Func<double, Grade> casesResolver = grade => 

   if (grade <= 2) return Grade.E;
   if (grade <= 4) return Grade.D;
   // the rest of the cases here...

   return Grade.A;


// finally
Grade generalGrade = casesResolver(overallAverage);
Console.WriteLine($"Overall Average: overallAverage - GRADE: generalGrade");

【讨论】:

【参考方案3】:

这不起作用,因为字典需要一个键类型作为第一个类型参数。这通常是字符串或 int 或 enum 类型。函数在这个地方不起作用,因为它不会作为字典查找的一部分进行评估,只会测试引用相等性。

您可以使用 switch expression (C# 8.0) 和 relational patterns (C# 9.0) 来制定这些条件

Grade grade = overallAverage switch 
    < 2 => Grade.E,
    < 4 => Grade.D
    < 6 => Grade.C
    < 8 => Grade.B
    _ => Grade.A
;

如果您的 C# 版本较旧,请使用一系列 if 语句:

Grade grade;
if (overallAverage < 2)  grade = Grade.E; 
else if (overallAverage < 4)  grade = Grade.D; 
else if (overallAverage < 6)  grade = Grade.C; 
else if (overallAverage < 8)  grade = Grade.B; 
else  grade = Grade.A; 

在这两种解决方案中,您都不必为最后一种情况测试条件,因为由于所有其他可能性都已处理,所以等级A 是唯一可以应用的等级。请注意,当条件匹配时,将不再评估后续条件。

仍然可以通过简单地使用具有平均值和相应等级的数组来使用基于数据的解决方案

(decimal average, Grade grade)[] limits =
     (2m, Grade.E), (4m, Grade.D), (6m, Grade.C), (8m, B) ;

Grade grade = Grade.A;
foreach (var limit in limits) 
    if (overallAverage  < limit.average) 
        grade = limit.grade;
        break;
    

m 作为数字后缀表示decimal

注意使用正确的比较运算符(&lt;&lt;=)。

【讨论】:

请注意,此语法仅在最近的 C# 版本中可用。

以上是关于使用枚举的 C# 字典的主要内容,如果未能解决你的问题,请参考以下文章

c#将枚举转换成字典集合

C# 使用定时器和Dictionary 出现异常说集合已修改,可能无法执行枚举操作,求帮忙啊

澄清 C# 字典上的读写

在 C# 中查询字典的优雅方式

JSON 序列化 - 将枚举转换为数字

java开发中使用枚举表述数据字典