Анонимные внутренние классы в C #? - PullRequest
8 голосов
/ 23 августа 2010

Есть ли что-то вроде анонимных внутренних классов (используемых в Java) в C #?

Я объясняю, для чего я буду использовать это на примере: я объявляю и инициализирую поле типа IDictionary<Person, Account>, и мне нужно написать custom IEqualityComparer<Person>. Это потому, что я хочу, чтобы два персонажа рассматривались как равные IDictionary, когда они имеют одинаковые имена и идентификаторы (не только идентификаторы, как это по умолчанию). Мне не понадобится этот 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 два Color считаются равными, когда все их свойства (кроме Id) равны.

Теперь, если я использую решение, которое Джон Скит любезно порекомендовал мне, я не уверен, что это можно обеспечить. Если бы я использовал что-то вроде:

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). Однако, поскольку я не могу переопределить Color Equals(object), он не будет сравнивать Color с, как я хочу (используя все свойства, кроме Id), поэтому равенство Font с будет проверено неправильно. Я прав?

Ответы [ 7 ]

13 голосов
/ 23 августа 2010

У меня есть класс 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 });

Это довольно уродливо, но оно должно работать ...

2 голосов
/ 24 августа 2010

В своем последнем редакторе вы упоминаете, что причина того, что вы не реализуете Equals и GetHashCode, заключается в том, что код для ваших классов генерируется автоматически, и вам не нужно повторять этот код каждый раз, когда вы обновляетекод.

Это один из сценариев, для которых частичные классы были введены в C #

. Многие инструменты генерации кода будут генерировать классы с частичным ключевым словом, чтобы позволить вамчтобы воспользоваться этой функцией.Проверьте, являются ли классы, которые генерируются для вашего кода, частичными.

В отдельном файле (или файлах), который не будет перезаписан при повторной генерации кода, в той же сборке, вы можете получить что-то вродеследующее:

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()
    {
        // ...
    }
}
2 голосов
/ 23 августа 2010

Буквальный ответ: нет, C # не имеет анонимных внутренних классов, потому что Java добавила их, чтобы обойти отсутствие первоклассных функций, которые есть в C #.Более конкретно, чтобы решить вашу проблему, вы можете просто реализовать IEquatable<Person> в своем классе Person, а затем IDictionary будет использовать это автоматически.Это наиболее распространенное решение этой проблемы, и оно работает до тех пор, пока вы согласны с процессом сравнения лиц, выпекаемых в этом классе.

Если вы хотите, чтобы логика сравнения / равенства не была напрямую связана с Personбольшинство коллекций в .NET позволяют вам передавать объект Comparison<T> (который является делегатом, а не интерфейсом), позволяя вам делать хорошую логику сортировки на месте.Например, чтобы отсортировать список людей по имени, вы можете сделать:

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

К сожалению, Dictionary не имеет ничего похожего на функцию равенства.В .NET 4.0 стандартный ответ должен переопределить EqualityComparer<T>:

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.

2 голосов
/ 23 августа 2010

Нет, нет.Существуют анонимные типы, например

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

, но они очень ограничены и содержат только свойства только для чтения и не содержат методов.

1 голос
/ 23 августа 2010

Самое близкое, что вы получите - это анонимные типы , как вы могли бы увидеть в выражении LINQ.Краткий пример по ссылке:

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

Определенно не то, что вы ищете.Я также не слышал о будущей поддержке анонимных классов в C #.

0 голосов
/ 23 августа 2010

Вы можете определить реализацию интерфейса в одном месте, 1 классе, сопоставить интерфейс с желаемым классом реализации в вашей любимой инфраструктуре IOC, и вам не нужно будет думать об экземпляре одноразовой анонимной реализации.

0 голосов
/ 23 августа 2010

Нет, на момент написания этого вопроса (C # 3.0) его не было.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...