C# 使用LINQ从类属性计算百分比的更好方法

C# 使用LINQ从类属性计算百分比的更好方法,c#,linq,C#,Linq,我们对LINQ有以下定义: myList.Select(s=> new DtoTest() { TotalSamples = myList.Count(c=> c.UserId == s.UserId), EvaluatedSamples = myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK) PercentageRealized = (myList.Count(

我们对LINQ有以下定义:

myList.Select(s=> new DtoTest()
{
    TotalSamples = myList.Count(c=> c.UserId == s.UserId),
    EvaluatedSamples = myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)
    PercentageRealized = (myList.Count(c=> c.UserId == s.UserId) / myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)) * 100
});
是否有一种方法可以在不使用之前在TotalSamples&EvaluatedSamples中使用的相同函数的情况下指定属性值PercentageRealized

诸如此类:

myList.Select(s=> new DtoTest()
{
    TotalSamples = myList.Count(c=> c.UserId == s.UserId),
    EvaluatedSamples = myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)
    PercentageRealized = (TotalSamples / EvaluatedSamples) * 100 //<-!Not possible!
});

任何其他提示?

首先投影到元组,然后投影到自定义对象:

 myList.Select(s => (tSample: myList.Count(c=> c.UserId == s.UserId), 
       eSample : myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)))
      .Select(x => new DtoTest
      {
             TotalSamples = x.tSample,
             EvaluatedSamples = x.eSample,
             PercentageRealized = (x.tSample / x.eSample) * 100 
     });
或使用匿名类型:

myList.Select(s => new 
      {
        tSample = myList.Count(c=> c.UserId == s.UserId),
        eSample = myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)
      })
      .Select(x => new DtoTest
      {
            TotalSamples = x.tSample,
            EvaluatedSamples = x.eSample,
            PercentageRealized = (x.tSample / x.eSample) * 100 
     });

首先投影到元组,然后投影到自定义对象:

 myList.Select(s => (tSample: myList.Count(c=> c.UserId == s.UserId), 
       eSample : myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)))
      .Select(x => new DtoTest
      {
             TotalSamples = x.tSample,
             EvaluatedSamples = x.eSample,
             PercentageRealized = (x.tSample / x.eSample) * 100 
     });
或使用匿名类型:

myList.Select(s => new 
      {
        tSample = myList.Count(c=> c.UserId == s.UserId),
        eSample = myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)
      })
      .Select(x => new DtoTest
      {
            TotalSamples = x.tSample,
            EvaluatedSamples = x.eSample,
            PercentageRealized = (x.tSample / x.eSample) * 100 
     });
如果PercentageRealized是简单的计算,为什么不在类的属性中进行计算,类似于类DTOTest中的以下内容:

public float PercentageRealized => (TotalSamples / EvaluatedSamples) * 100;
如果PercentageRealized是简单的计算,为什么不在类的属性中进行计算,类似于类DTOTest中的以下内容:

public float PercentageRealized => (TotalSamples / EvaluatedSamples) * 100;

更改函数委托以使用已计算的值

myList.Select(s => {
    var result = new DtoTest() {
        TotalSamples = myList.Count(c => c.UserId == s.UserId),
        EvaluatedSamples = myList.Count(c => c.UserId == s.UserId && c.Status == Status.OK)
    };
    result.PercentageRealized = (result.TotalSamples / result.EvaluatedSamples) * 100;
    return result;
});

更改函数委托以使用已计算的值

myList.Select(s => {
    var result = new DtoTest() {
        TotalSamples = myList.Count(c => c.UserId == s.UserId),
        EvaluatedSamples = myList.Count(c => c.UserId == s.UserId && c.Status == Status.OK)
    };
    result.PercentageRealized = (result.TotalSamples / result.EvaluatedSamples) * 100;
    return result;
});

如果您使用的是匿名类型,这将更加复杂,但由于DtoTest是一个类,因此您始终可以将数学移到属性中

public class DtoTest
{
    public float PercentageRealized
    {
        get { return (TotalSamples / EvaluatedSamples) * 100; }
    }
}

如果您使用的是匿名类型,这将更加复杂,但由于DtoTest是一个类,因此您始终可以将数学移到属性中

public class DtoTest
{
    public float PercentageRealized
    {
        get { return (TotalSamples / EvaluatedSamples) * 100; }
    }
}

您所做的对我来说似乎极为可疑-您对源数据进行多次传递,以便在每次出现UserId时一遍又一遍地重新计算相同的内容,而您似乎应该希望每个UserId计算一次,如下所示:

var ans2 = myList.GroupBy(s => s.UserId)
                .Select(sg => {
                    var ts = sg.Count();
                    var es = sg.Count(c => c.Status == Status.OK);
                    return new DtoTest { UserId = sg.Key, TotalSamples = ts, EvaluatedSamples = es, PercentageRealized = (int)(100.0 * ts / es) };
                });
此外,除非先转换为双精度,否则百分比计算将使用C整数除法,不会接近正确。你可以在数学运算后返回int

如果您真的想要返回多个结果,并且希望高效,并且因为我喜欢扩展方法,请创建一个扩展方法来计算一次传递的链接谓词:

public static class IEnumerableExt {
    public static (int Cond1Count, int Cond2Count) Count2Chained<T>(this IEnumerable<T> src, Func<T, bool> cond1, Func<T, bool> cond2) {
        int cond1Count = 0;
        int cond2Count = 0;
        foreach (var s in src) {
            if (cond1(s)) {
                ++cond1Count;
                if (cond2(s))
                    ++cond2Count;
            }
        }
        return (cond1Count, cond2Count);
    }
}
当然,根据myList的大小,您可能还是最好先计算每个答案一次,然后重复它们以获得最终答案:

var ansd = myList.GroupBy(s => s.UserId)
                 .Select(sg => {
                     var ts = sg.Count();
                     var es = sg.Count(c => c.Status == Status.OK);
                     return new { sg.Key, ts, es };
                 })
                 .ToDictionary(ste => ste.Key, ste => new DtoTest {
                    UserId = ste.Key,
                    TotalSamples = ste.ts,
                    EvaluatedSamples = ste.es,
                    PercentageRealized = (int)(100.0 * ste.ts / ste.es) });
var ans4 = myList.Select(s => ansd[s.UserId]);

您所做的对我来说似乎极为可疑-您对源数据进行多次传递,以便在每次出现UserId时一遍又一遍地重新计算相同的内容,而您似乎应该希望每个UserId计算一次,如下所示:

var ans2 = myList.GroupBy(s => s.UserId)
                .Select(sg => {
                    var ts = sg.Count();
                    var es = sg.Count(c => c.Status == Status.OK);
                    return new DtoTest { UserId = sg.Key, TotalSamples = ts, EvaluatedSamples = es, PercentageRealized = (int)(100.0 * ts / es) };
                });
此外,除非先转换为双精度,否则百分比计算将使用C整数除法,不会接近正确。你可以在数学运算后返回int

如果您真的想要返回多个结果,并且希望高效,并且因为我喜欢扩展方法,请创建一个扩展方法来计算一次传递的链接谓词:

public static class IEnumerableExt {
    public static (int Cond1Count, int Cond2Count) Count2Chained<T>(this IEnumerable<T> src, Func<T, bool> cond1, Func<T, bool> cond2) {
        int cond1Count = 0;
        int cond2Count = 0;
        foreach (var s in src) {
            if (cond1(s)) {
                ++cond1Count;
                if (cond2(s))
                    ++cond2Count;
            }
        }
        return (cond1Count, cond2Count);
    }
}
当然,根据myList的大小,您可能还是最好先计算每个答案一次,然后重复它们以获得最终答案:

var ansd = myList.GroupBy(s => s.UserId)
                 .Select(sg => {
                     var ts = sg.Count();
                     var es = sg.Count(c => c.Status == Status.OK);
                     return new { sg.Key, ts, es };
                 })
                 .ToDictionary(ste => ste.Key, ste => new DtoTest {
                    UserId = ste.Key,
                    TotalSamples = ste.ts,
                    EvaluatedSamples = ste.es,
                    PercentageRealized = (int)(100.0 * ste.ts / ste.es) });
var ans4 = myList.Select(s => ansd[s.UserId]);


这类似于let的实现,只使用ValueTuple而不是匿名对象。@NetMage是的,也可以使用匿名类型。包含是为了完整性。我的意思是在LINQ查询语法中,let运算符是通过将Select投影到匿名类型来实现的。@Aomine Good approach!:这类似于let的实现,只使用ValueTuple而不是匿名对象。@NetMage是的,也可以使用匿名类型。包含是为了完整性。我的意思是在LINQ查询语法中,let运算符是通过将Select投影到匿名类型来实现的。@Aomine Good approach!:大概我考虑过,但在课堂上这样做的问题是,在我的项目中,这是一个DTO,他们没有任何规则/计算。是的,我理解你的观点,有意义,在这种情况下,我可能会选择Nkosi解决方案!我考虑过,但在课堂上这样做的问题是,在我的项目中,这是一个DTO,他们没有任何规则/计算。是的,我理解你的观点,有意义,在这种情况下,我将使用Nkosi解决方案,这正是我所理解的。。。如何在LINQ语句中创建变量。谢谢你的回答!这正是我所理解的。。。如何在LINQ语句中创建变量。谢谢你的回答!非常感谢您关注每一点,并提供了一个更加专注的答案!事实上,我需要一份GroupBy声明,因为我忘了问这个问题。在计算中,是的,我们已经确定我们需要转换通过函数获得的结果。Count我们正在使用float的属性。最后一个注意事项是,我需要填充DtoTest的UserId,使用新的DtoTest有什么问题吗{…UserId=s.FirstOrDefault.UserId?谢谢你的帮助!@Igor因为GroupBy是按UserId的,所以你可以使用sg.Key来获取DtoTest的组的UserId。我会编辑答案。好吧,对于这个属性,它工作得很好。Key…但是问题是我有更多需要的属性。FirstOrDefault比如:UserName,等等。我不使用e问题,因为他们会的
总是一样的。对吗?比如:.GroupBygb=>new{gb=>gb.UserId,gb.UserName}。。。新数据测试{…UserId=s.Key.UserId,s.Key。UserName@Igor是的,您可以按附加属性分组,但我建议在这种情况下使用FirstOrDefault,就像您最初打算用于附加属性一样。非常感谢您关注每一点,并提供了一个更专门的答案!事实上,我需要一个GroupBy声明,我已经orgotten提出问题。在计算中,是的,我们已经确定需要转换通过函数获得的结果。Count我们正在使用带有float的属性。最后一个注意事项是,我需要填充DtoTest的UserId,使用新的DtoTest是否有问题{…UserId=s.FirstOrDefault.UserId?谢谢你的帮助!@Igor因为GroupBy是按UserId的,所以你可以使用sg.Key来获取DtoTest的组的UserId。我会编辑答案。好吧,对于这个属性,它工作得很好。Key…但是问题是我有更多需要的属性。FirstOrDefault比如:UserName,等等。我不使用问题,因为它们总是相同的,即我可以根据这些属性中的任何一个进行分组。对吗?比如:.GroupBygb=>new{gb=>gb.UserId,gb.UserName}…new DtoTest{…UserId=s.Key.UserId,s.Key。UserName@Igor是的,您可以按附加属性分组,但我建议在这种情况下使用FirstOrDefault,就像您最初打算用于附加属性一样。