C#泛型效率,更好的实现方式

3

好的,假设我有以下类:

public class KPIObject<T> //<--This class where T is the following classes
{
    public List<T> Data { get; set; }
    public string Caption { get; set; }
}

public class KPICycleCountAccuracyData //<--There are 20 of these with different names and values
{
    public string Facility { get; set; }
    public string CCAdjustedCases { get; set; }
    public string TotalCases { get; set; }
    public string CCAdjustedPercent { get; set; }
}

然后我有:
public List<ReportData>> ProcessAccountReport(GetAccountReport request)
{
    var data = new List<ReportData>();
    ProcessKPI(data, request.KPICycleCountAccuracy, "KPICycleCountAccuracy"); //<-- 20 of these
    return data;
}

以下是ProcessKPI方法:

private static void ProcessKPI<T>(List<ReportData> data, ICollection<KPIObject<T>> items, string name)
{
    if (items == null || items.Count <= 0) return;
    foreach (var item in items)
    {
        if (item.Data == null || item.Data.Count <= 0) continue;
        var temp = new List<object>();
        temp.AddRange((IEnumerable<object>)item.Data);
        data.Add(new ReportData { Data = temp, Name = name, Title = item.Caption });
    }
}

所有的代码都能够正常工作和编译,但我想知道这是否是最有效的方法。
谢谢。
编辑
我将进程KPI更改为以下内容:
private static void ProcessKPI<T>(ICollection<ReportData> data, ICollection<KPIObject<T>> items, string name)
        {
            if (items == null || items.Count <= 0) return;
            foreach (var item in items.Where(item => item.Data != null && item.Data.Count > 0))
            {
                data.Add(new ReportData { Data = (IEnumerable<object>)item.Data, Name = name, Title = item.Caption });
            }
        }

只是为了明确您所寻求的效率增益类型:您是在说您正在调用20次 ProcessKPI()(每个20中类型代码片段中都有一次调用),如果您再添加一种类型,则必须添加一次 ProcessKPI() 调用,等等。您正在寻找一种更可扩展的编写此代码的方法? - KP Taylor
性能、代码量少、内存问题。是的,如果我添加另一个类,我必须再进行一次ProcessKPI调用。 - Cyberdrew
1
不确定它的长度,但我知道你的数据参数不需要是一个 ref,因为它不是一个值类型。 - Corey Coogan
好的,我已经移除了 ref 并且改变了 IEnumerable 的位置。 - Cyberdrew
2个回答

1

几点评论

  • ProcessKPI中不需要将data设置为ref参数。在C#中,只有当你实际分配给它时,ref参数对于class类型才有意义。在这里,您只是修改对象,因此ref除了笨拙的调用语法外没有任何作用。
  • 即使Count是有符号的,它也永远不会返回负值。
  • 我更喜欢使用(IEnumerable<object>)item.Data而不是as IEnumerable<object>版本。如果后者失败,它将导致ArgumentNullException,而实际上它是一个转换问题。

我移除了 ref 并改变了 IEnumerable 的位置。谢谢。 - Cyberdrew

1

速度 假设您谈论的是计算效率(即速度),那么有两个操作可能可以改进:

首先,您在temp变量中创建了item.Data的副本。当您知道生成的ReportData永远不会被修改时,您可以直接使用item.Data,避免昂贵的复制操作。

data.Add(new ReportData { Data = (IEnumerable<object>)item.Data, Name = name, Title = item.Caption });

其次,转换为IEnumerable<object>可能会在以后的某个时刻导致不必要的装箱/拆箱。看看是否有意义为ReportData添加一个泛型类型参数,这样您就可以将其实例化为new ReportData<KPIObject>()。这样编译器可以更好地优化代码。

内存 通过使用continuations实现您的解决方案,您可以一次处理一个ReportData元素,而不是一次性处理所有元素,从而减少内存占用。请查看{{link1:yield}}语句,了解如何实现这种方法。

其他 为了进一步提高代码质量,JaredPar的答案提供了一些很好的建议。


1
我想你可能有所误解,我并没有在这个方法中看到任何复制操作。列表操作都是引用,字符串复制也是引用。所以他最多只是复制了一些指针地址。此外,除非 OP 正在使用 List<int> 等类型,否则我认为你关于装箱的说法是不成立的,因为对于引用类型根本不会进行装箱操作。对此我给出 -1 的评分,但对于 yield 的信息我给出 +1 的评分。 - Chris Marisic
1
@ChrisMarisic AddRange()确实会复制所有引用,而不是完整的对象。我并不是想暗示所有字符串都被复制了。但在每次迭代中仍然需要复制那20个引用;这些引用可能存在于List<T>的实现方式不连续的内存区域中。它的成本可能很小,但是省略AddRange()的复制操作肯定比保留它要快。 - Joseph Tanenbaum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接