【问题标题】:C# Linq alternative for lodash omit functionlodash 省略函数的 C# Linq 替代方案
【发布时间】:2016-10-14 10:53:54
【问题描述】:

示例输入:

// remove sensitive data    
var user = db.Users.Where(x => x.Id == '123').ToList()
      .Omit("PasswordHash", "PasswordSalt", "SecretDataAsInteger");

输出:

{
  FirstName: "...",
  LastName: "...",
  PasswordHash: "", // default value instead of real value
  PasswordSalt: "",
  SeretDataAsInteger: 0
}

类似于: https://lodash.com/docs/4.16.4#omit

偏好:

  • 我更喜欢不选择新对象并设置每个属性的解决方案,除了我想省略的属性,即

var user = ..Users.Select(x => new User {/* here I set each property */})

由于这导致更新所有参考,以防我稍后更新User模型属性

  • 在省略函数中传递对象而不是属性名称作为字符串也是可以接受的

【问题讨论】:

  • 问题到底是什么? Omit的方法如何实现?
  • @JonSkeet 我不认为 omit 方法会修改任何东西,我认为它基本上是“给我所有没有这些指定属性的对象”,对于排除密码字段或我想的敏感数据很有用。跨度>
  • @DavidG:据我所知,这不是省略对象,而是省略属性...所以是的,它仍然可以等同于Select,但就是这样,而不是Where...
  • 听起来你基本上想要通过反射复制属性的东西,但省略了某些属性。 stackoverflow.com/questions/1198886 看起来很相关。
  • @hvd:是的,我同意,我误读了那部分。所以 使用 LINQ 是一件合理的事情。一个简短但完整的例子肯定会让这一切变得更清楚,IMO...(将删除评论。)

标签: c# linq lodash


【解决方案1】:

下面是我为您创建的一个类,它应该使用提供的属性名称列表,应该将它们设置为属性类型的默认值。使用您的示例:

// remove sensitive data    
var user = db.Users.Where(x => x.Id == '123').ToList()
  .OmitProperties("PasswordHash", "PasswordSalt", "SecretDataAsInteger");

记得添加System.Reflection

public static class EnumerableExtensions {
        public static IEnumerable<T> OmitProperties<T>(this IEnumerable<T> enumerable, params string[] propertyNames) {
            PropertyInfo[] propertiesToOmit = typeof(T).GetProperties()
                .Where(p => propertyNames.Contains(p.Name))
                .ToArray();

            foreach (var item in enumerable) {
                foreach (var property in propertiesToOmit) {
                    property.SetValue(item, null);
                }

                yield return item;
            }
        }
    }

【讨论】:

  • 有段时间没写高阶C#了,想问一下:这样用反射是不是特别慢?例如,如果我写这样的东西而不是db.Users.Where(x =&gt; x.Id == 123).Select(x =&gt; new User { Name = x.Name, Id = x.Id }) - (手动省略),那不是更快吗? (虽然没有回答答案...)
  • 我同意手动省略属性而不是使用它会更快。然而,在每次创建User 类的新实例时,可以说会占用更多内存(在 GC 或对象创建中),并可能导致其运行速度变慢。取决于答案。
  • @LukeFisher 创建User 类的新实例或匿名类型的新实例很便宜:它降低了原始User 对象的成本,因为这会变得很短- 在没有任何参考的情况下生活。 GC 非常擅长摆脱这些。当然比为每个用户使用反射更便宜。
  • @hvd 从来没有进入 .NET 分配器的内部,但这听起来很合理。无论如何,我猜最快的方法可能是手动循环并设置所有需要的属性。
  • 基于问题,我认为他们将需要使用反射方法将属性设置为“默认”。我假设他们希望出于任何目的保留现有的User,只是“取消”他们指定的属性。
【解决方案2】:

如果您可以控制User 类,则可以添加一个采用User 的构造函数。使此构造函数复制所有字段。您可以使用反射轻松做到这一点。如果您通过动态生成完成所需工作的辅助方法来实现这一点,则辅助方法只需要生成一次。给定这个构造函数,你可以写

var user = db.Users.Where(x => x.Id == 123).AsEnumerable()
  .Select(x => new User(x) {
    PasswordHash = "",
    PasswordSalt = "",
    SecretDataAsInteger = 0
  }).ToList();

这保证了它的类型安全:你不能意外地忽略 User 没有的属性(例如,你试图忽略 PasswordSlat 而不是 PasswordSalt),并且你不能通过将属性设置为属性类型不支持的值(例如,试图通过将其设置为 null 来省略 SecretDataAsInteger),并且无论如何尝试这样做都会被编译器标记,而不是在运行时崩溃。

【讨论】:

  • 感谢您提及这一点,但在我的情况下,我宁愿不为此创建一个 ctor。
  • 如果您跨越安全边界,应该有一个全新的对象,而不仅仅是一个构造函数。
【解决方案3】:

这是一个不是 Linq 的选项,它实际上更新了基础对象。可能不是您想要的,但它可以完成工作:

public static IEnumerable<T> ApplyToAll<T>(this IEnumerable<T> sequence, 
    params Action<T>[] action)
{
    foreach (var element in sequence)
    {
        foreach (var action in actions)
        {
            omission(action);
        }
        yield return element;
    }
}

而不是传递属性的字符串名称,而是传递一些作用于输入的动作:

var redactedUsers = users.ApplyToAll(
    u => u.Password = "",
    u => u.Name = "Hello " + u.Name);

【讨论】:

  • 感谢您提及这一点,但是在这种情况下,我更愿意将其命名为 Apply 或其他名称,而不是 Omit
  • 其实你是对的,一个更好的名字应该是那个或者ApplyToAll
猜你喜欢
  • 2020-04-14
  • 2016-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-25
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多