записи C# 9.0 - отражение и общие ограничения

Два вопроса о новых записях:

  1. Как распознать запись с помощью отражения? глядя сюда, может быть, есть способ обнаружитьEqualityContract но я не уверен, что это путь?

  2. Возможно ли иметь общее ограничение, что универсальный тип является записью? то есть можно ли указать, что параметр типа T должен быть классом записи с использованием ограничения?

Обоснование - я пытаюсь написать код, чтобы проверить, является ли тип "данными" в функциональном смысле, то есть (1) неизменяемым и (2) имеет семантику значений.

Я начинаю с небольшого списка основных / основных / занесенных в белый список типов, которые являются "данными" (например, "int" или "DateTime"). Чтобы проверить неизменность, я могу проверить, что все поля и автоматические реквизиты доступны только для чтения / без сеттера и сами являются "данными" (начиная с приведенного выше списка) и т. Д.

Проверка семантики значения сложнее, однако я могу разрешить только указанные выше типы + записи, содержащие их как recordсемантика гарантийного значения (я могу проверить, нет ли определенных пользователем 'Equals' и 'GetHashCode'). Вот почему мне нужно проверить, является ли тип записью.

6 ответов

Как уже упоминалось всеми, невозможно написать

private void MyFunc<T>(T t) where T : record {...}

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

public abstract record RecordMarker;
public record MyRecord : RecordMarker;
public void MyFunc<T>(T t) where T : RecordMarker
{
}

При этом вы можете передавать только типы записей, поскольку классы не могут наследовать от записей.

MyFunc(new MyRecord()); // Works
MyFunc(new MyClass());  // Compiler Error

Если вы попробуете записать классы в sharplab.io вы увидите, что классы записи - это обычные классы, реализующие IEquatable<T>интерфейс и содержат дополнительные члены, которые используются для сравнения и клонирования экземпляров класса записи. Нет никаких атрибутов, указывающих, что класс являетсяrecord class.

Поэтому я считаю, что с помощью отражения невозможно определить, является ли класс классом записи.


Второй вопрос немного непонятен.

Если вы спросите, можно ли использовать дженерики с классами записей и указать для них ограничения, то ответ - ДА, это возможно. Классы записи - это обычные классы, и с ними можно использовать дженерики (опять же, вы можете попробовать это в sharplab.io).

Если вы спросите, можно ли указать этот параметр типа TДолжен быть класс записи, тогда я считаю, что ответ - НЕТ. Страница предложения записей не содержит никакой информации об указании параметра универсального типа.T должен быть класс записи.

В качестве "взлома" все записи имеют синтезированный метод <Clone>$который вы можете искать. Поскольку вы не можете написать метод с таким именем на C#, класс с <Clone>$член гарантированно будет записью начиная с C# 9.

Однако нет никаких гарантий, что так будет и дальше. Например, возможно, что в C# 10.0 некоторые записи не будут иметь <Clone>$ член, или что некоторые не записи будут.

public static bool IsRecord(Type type) => type.GetMethod("<Clone>$") != null;

Как распознать запись с помощью отражения?

Как указано здесь и здесь

Это не только не официальный способ сделать это, но и явно противоречит дизайну функции. Намерение для записей состоит в том, чтобы, надеюсь, с C# 10 мы дойдем до точки, когда создание класса записью будет исключительно удобным выбором, и что любая другая часть функции будет достигнута с помощью некоторой формы синтаксиса. Изменение типа с записи на класс не должно быть критическим изменением, и мы даже представляем, что рефакторинг IDE может автоматически перемещать тип в синтаксис записи и обратно, незаметно для клиентов. Для C# 9 есть места, где мы этого не совсем достигли, но это цель.

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

Вот некоторые хакерские способы обнаружения записей, с которыми работает банкомат:

  1. проверьте, есть ли EqualityContract собственность с CompilerGenerated атрибут
isRecord = ((TypeInfo)t).DeclaredProperties.Where(x => x.Name == "EqualityContract").FirstOrDefault()?.GetMethod?.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) is object;
  1. проверить <Clone>$ член, как указано @Yair Halberstadt
isRecord = t.GetMethod("<Clone>$") is object;

или комбинация обоих

Возможно ли иметь общее ограничение, что универсальный тип является записью?

Нет

Кажется, очень разумный вопрос, несмотря на некоторые ответы здесь. Реальность такова, что запись — это просто синтаксический сахар над классом, и ее можно создать множеством других способов.

Одним из вариантов может быть создание пустого базового класса или интерфейса записи маркера, даже если он пуст, и добавление к нему ограничения. Это обеспечит требуемое ограничение.

Если вы наследуете от этого класса все классы, которые хотите включить в ограничение, это будет работать нормально.

isRecord = t.GetMethod("$") - объект; ==> isRecord = t.GetMethod("$") не равно нулю;

Другие вопросы по тегам