【问题标题】:Anonymous inner classes in C#?C# 中的匿名内部类?
【发布时间】:2010-08-22 20:30:48
【问题描述】:

在 C# 中是否有类似匿名内部类(在 Java 中使用)的东西?

我举例说明我会用它做什么:我正在声明和初始化IDictionary<Person, Account> 类型的字段,我需要编写自定义IEqualityComparer<Person>。那是因为我希望 IDictionary 将两个 Person 视为相同,因为它们具有相同的名称和 ID(不仅是默认情况下的 ID)。我在代码中的任何其他地方都不需要这个IEqualityComparer<Person>

所以我是否必须声明实现IEqualityComparer<Person> 的新类才能做到这一点?在 Java 中,我会使用匿名类,类似这样(这是混合的 C#-Java 语法,只是为了显示我正在寻找的功能):

IDictionry<Person, Account> myDict = new Dictionary<Person, Account>(
    new IEqualityComparer<Person>(){
        public bool Equals(Person a, Person b){
            return a.Id == b.Id && a.Name == b.Name;
        }

        public int GetHashCode(Person p){
            return p.Id.GetHashCode() * p.Name.GetHashCode();
        }
    });

在 C# 中有这样的东西吗?每次我需要这样的东西时,我都懒得写新课程。

注意:这是语法问题。我知道怎么写,但我想知道是否有可能使代码更短。

------------------------------------------ -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------

编辑:您自己如何编写类似案例的代码?您是创建新类来实现接口还是做什么?也许你有一些我可能会喜欢的技巧。

编辑 未来对匿名类(如 Java 中的那些)的支持如何?你听说过吗?

编辑:好吧,我知道我必须提供我的实际代码 - 而不仅仅是一个示例。那是因为我不知道它是否适用于 Jon's Skeet 的解决方案。

我不只是在类本身中实现Equals(object)GetHashCode 的实际原因是,它是E-R 框架从模型图中生成的类(实体)。如果我在类本身中实现它,那么每次我从数据库更新模型(使用“从数据库更新”功能)时,我的代码都会从类(实体)中删除。该类实际上称为Font 而不是Person。它具有以下特性:

Id: int
FamilyName:string
Size:int
Bold:bool
Italic:bool
Underlined:bool
Striked:bool
Foreground:Color

其中Color 是从数据库生成的另一个类(实体)。

这是颜色的属性:

Id:int
Alpha:byte
Red:byte
Green:byte
Blue:byte

所以我不能修改字体,颜色也不能(如果我不想在每次更改数据库时一遍又一遍地重写这些更改)我想要的是拥有这个Dictionary

private IDictionary<Font, Something> cache = new Dictionary<Font, Something>(new SomeEqualityComparer());

并且比较器SomeEqualityComparer 应该确保当且仅当上面列出的所有属性(Id 除外)都相等时,两个字体才会被视为相等。在最后一个属性Foreground 的情况下,当两个Colors 的所有属性(Id 除外)都相等时,它们被认为是相等的。

现在,如果我使用 Jon Skeet 向我推荐的解决方案,我不确定这是否可以确保。 如果我使用类似的东西:

private IDictionary<Font, Something> cache = new Dictionary<Font, Something>(ProjectionEqualityComparer<Font>.Create
(f => new { f.FontName, f.Size, f.Bold, f.Italic, f.Underlined, f.Striked, f.Foreground});

我猜想匿名类型在调用Equals(object) 时在所有属性上调用Equals(object)。但是,由于我无法覆盖ColorEquals(object),它不会像我想要的那样比较Colors(使用除Id 之外的所有属性),因此Fonts 的相等性也会被错误地测试。我说的对吗?

【问题讨论】:

    标签: c# java syntax


    【解决方案1】:

    我有一个 ProjectionEqualityComparer 类,你可以在 MiscUtil 中使用。你会使用这样的代码:

    IEqualityComparer<Person> comparer = ProjectionEqualityComparer<Person>.Create
        (p => new { p.Name, p.Id });
    

    这利用了匿名类型内置了适当的相等概念这一事实 - 当ProjectionEqualityComparer 被要求比较两个人是否相等时,它会将每个人投影到匿名类型,并比较这些实例。同样,当它被要求提供哈希码时,它会执行投影并要求其提供哈希码。

    编辑:要解决您的颜色问题,您是对的:如果 Color 没有以您想要的方式覆盖 Equals/GetHashCode,您就不能直接使用它。但是,您可以改为这样做:

    private IDictionary<Font, Something> cache = new Dictionary<Font, Something>
       (ProjectionEqualityComparer<Font>.Create(f => new { 
            f.FontName, f.Size, f.Bold, f.Italic, f.Underlined, f.Striked, 
            f.Foreground.Alpha, f.Foreground.Red, f.Foreground.Green,
            f.Foreground.Blue});
    

    如果您能够根据属性修改 Color 类型,那么如果您可以给它一个从其他属性生成的 ARGB 属性会更简单,因此您可以编写:

    private IDictionary<Font, Something> cache = new Dictionary<Font, Something>
       (ProjectionEqualityComparer<Font>.Create(f => new { 
            f.FontName, f.Size, f.Bold, f.Italic, f.Underlined, f.Striked, 
            f.Foreground.ARGB });
    

    这很丑陋,但它应该可以工作......

    【讨论】:

    • 我发现也许我不能使用这个解决方案来解决我原来的问题(除了 cmets 中的理论讨论)。请检查我在原始问题中的第三次编辑 - 问题的描述太长而无法在评论中写出来。我希望我的解释能被理解。谢谢
    • 谢谢。其他任何事情都会比这更丑陋。所以我打算这样做。
    【解决方案2】:

    在您上次的编辑中,您提到您不实现 Equals 和 GetHashCode 的原因是因为您的类的代码是自动生成的,您不希望每次重新生成时都重新实现该代码代码。

    这是partial classes were introduced in C# 的场景之一

    许多代码生成工具会生成带有 partial 关键字的类,以便您利用该功能。检查为您的代码生成的类是否是部分的。

    在重新生成代码时不会被覆盖的单独文件(或多个文件)中,在同一个程序集中,您可能具有以下内容:

    partial class Font
    {
        public override bool Equals(object obj)
        {
            // ...
        }
    
        public override int GetHashCode()
        {
            // ...
        }
    }
    
    partial class Color
    {
        public override bool Equals(object obj)
        {
            // ...
        }
    
        public override int GetHashCode()
        {
            // ...
        }
    }
    

    【讨论】:

    • 就我而言,我认为这是最好的解决方案(它干净快捷,部分类确实是为此目的而制作的......)。接受。
    【解决方案3】:

    不,没有。有匿名类型,例如

    var MyType = new { id=1, name="john", dept = "sales" };
    

    但它们非常有限,并且只包含只读属性,没有方法。

    【讨论】:

    • 好的,我该怎么办?我必须为此创建新课程吗?还有其他方式吗(代表、labda 表达式或其他方式)?
    • 我会说 jon skeet 的 ProjectionEqualityComparer 课程是你最好的选择。
    【解决方案4】:

    字面上的答案是,不,C# 没有匿名内部类,因为 Java 添加了这些来解决 C# 确实缺少的一流函数的不足。更具体地说,要解决您的问题,您可以在您的 Person 类上实现 IEquatable&lt;Person&gt;,然后 IDictionary 将自动使用它。这是此问题最常见的解决方案,只要您同意比较被烘焙到该类中的人员的过程,它就可以工作。

    如果您希望比较/相等逻辑不直接绑定到 Person,.NET 中的大多数集合都允许您传入 Comparison&lt;T&gt; 对象(这是一个委托,而不是一个接口),让您这样做不错的就地排序逻辑。例如,要按姓名对人员列表进行排序,您可以这样做:

    List<Person> people = ...
    people.Sort((x, y) => x.Name.CompareTo(x.y));
    

    不幸的是,Dictionary 没有类似于相等函数的东西。在 .NET 4.0 中,常用答案似乎是覆盖 EqualityComparer&lt;T&gt;

    public class PersonComparer : EqualityComparer<Person>
    {
        public override bool Equals(Person a, Person b)
        {
            return a.Id == b.Id && a.Name == b.Name;
        }
    }
    

    但是,每次需要比较时都必须定义一个新类,这是一件苦差事。我要做的是制作一个带有函数的通用函数:

    public class Equality<T> : EqualityComparer<T>
    {
        public Equality(Func<T, T, bool> comparer)
        {
            this.comparer = comparer;
        }
    
        public override bool Equals(T a, T b)
        {
            return comparer(a, b);
        }
    
        private Func<T, T, bool> comparer;
    }
    

    添加一个小助手类:

    public static class Equality
    {
        public static Equality<T> Create<T>(Func<T, T, bool> comparer)
        {
            return new Equality<T>(comparer);
        }
    }
    

    然后你的解决方案变成:

    IDictionary<Person, Account> myDict = new Dictionary<Person, Account>(
        Equality.Create((a, b) => a.Id == b.Id && a.Name == b.Name);
    

    比 Java 还要短。

    【讨论】:

    • -1 更短,但也完全损坏 - 因为您没有覆盖 GetHashCode。此外,您不能在仅提供 Func&lt;T, T, bool&gt; 时覆盖 GetHashCode,除非覆盖仅返回 0。如果您实施可行的方法,您会很高兴地撤销反对票……但您需要一个Func&lt;T, int&gt; 以及...或类似于我的 ProjectionEqualityComparer 的东西(我个人认为它更干净,但显然我有偏见)。
    • @Jon Skeet:您已经提出了我想到的解决方案:构造函数将采用 2 个参数,第一个保持不变,第二个将是 Func&lt;T, int&gt; 类型,它将用于 @987654337 @。你可能是对的,ProjectionEqualityComparer 更简洁一些,但它消除了一些可变性 - 有时你可能想使用 3 个属性进行相等比较,但在 GetHashCode 中只会使用其中的 2 个。所以我更喜欢这个解决方案(只是添加了 GetHashCode)。 +1 好主意
    • @drasto:我想说这会增加犯错的机会 - 特别是,当使用 3 个属性表示相等而 2 个属性表示哈希码是有效的(尽管它给出了一个糟糕的哈希),使用相等的 2 个属性和哈希码的 3 个属性将是无效的,但 显然 不是这样。你能想出一个实际上你想要做的情况吗?我宁愿选择适用于 99% 情况的解决方案,并且比更灵活但更难使用的解决方案更容易正确。请注意,使用投影您只指定一件事,并且只处理一个对象。 (继续。)
    • 在这里使用“双功能”版本,哈希码生成只需要处理一个对象,但比较必须处理两个 - 你得到指定Name和@的冗余987654340@ 两次然后你必须得到正确的散列函数,考虑到Name 为空等的可能性。增加的复杂性真的值得你的灵活性'不太可能需要?
    • 再一次,你可能是对的。但是,我可以想到一种情况,即我只想在 GetHashCode 中使用一些属性(来自定义相等的属性集)。当属性之一是数组或集合时,可能会出现这种情况,您需要在元素后迭代和测试相等元素。假设您知道数组或集合可以很长,而其他属性很可能与不相等的对象不同。 (继续)
    【解决方案5】:

    您将获得的最接近的是anonymous types,就像您在 LINQ 表达式中看到的那样。链接中的一个简短示例:

    var v = new { Amount = 108, Message = "Hello" };
    

    绝对不是您想要的。我也没有听说过未来对 C# 中匿名类的支持。

    【讨论】:

      【解决方案6】:

      您可以在一个地方定义接口的实现,1 个类,将接口映射到您最喜欢的 IOC 框架中所需的实现类,而根本不必考虑实例化一次性使用的匿名实现。

      【讨论】:

        【解决方案7】:

        没有,截至最初编写此问题时(C# 3.0),还没有。

        【讨论】:

        • 这真的是评论,而不是问题的答案。请使用“添加评论”为作者留下反馈。
        • @Pratik 被拒绝。我为答案增加了一些额外的清晰度。我知道如何使用这个网站,伙计:)
        猜你喜欢
        • 2011-06-13
        • 2011-07-06
        • 1970-01-01
        • 1970-01-01
        • 2010-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-08-29
        相关资源
        最近更新 更多