C#Hive: Projects & Progress | Программирование
Open in Telegram
Сообщество единомышленников C#: решаем задачи, учимся, развиваемся и общаемся вместе. Советы по работе на фрилансе, готовые проекты, код ревью, рекомендации и исследования. Вопросы/сотрудничество: @tel_phil9
Show more1 780
Subscribers
No data24 hours
-17 days
-1030 days
Posts Archive
🖥 Частичные классы и методы
Классы могут быть частичными. Разделяемые классы позволяют нескольким частям одного и того же класса располагаться в разных файлах, но при компиляции все эти части будут собраны воедино. Для определения разделяемого класса используется ключевое слово partial, которое должно находиться непосредственно перед словом class.
Например, определим в проекте два файла с кодом. Не столь важно как эти файлы будут называться. Например, ClassBase.cs и ClassAdditional.cs. В одном из этих файлов (без разницы в каком именно) определим следующий класс:
public partial class ClassA
{
public void Foo()
{
Console.WriteLine("Method Foo");
}
}
А в другом файле определим следующий класс:
public partial class ClassA
{
public void Bar()
{
Console.WriteLine("Method Bar");
}
}
Таким образом, два файла в проекте содержат определение одного и того же класса ClassA, которые содержат два разных метода. Оба определённых здесь класса являются частичными.
Затем мы можем использовать все методы класса ClassA:
ClassA classA = new();
classA.Foo();
classA.Bar();
➡️ Частичные методы
Разделяемые методы практически аналогичны разделяемым классам: в одной части разделяемого класса объявляют сигнатуру метода, а в другой части разделяемого класса объявляют тело. Разделяемый метод должен начинаться с ключевого слова patrial.
Например, изменим классы ClassA. Первый класс:
public partial class ClassA
{
public partial void Tester();
}
Второй класс:
public partial class ClassA
{
public partial void Tester()
{
Console.WriteLine("Method Tester");
}
}
В первом классе определён метод Tester(). Причём на момент определения первого класса нам неизвестно, что представляет собой этот метод и какие действия он будет выполнять. Однако известен список его параметров.
➡️ Глобальный смысл таких возможностей
На практике с этим сталкиваешься редко, поэтому многие могут даже не знать об этих фичах. Для понимания смысла, стоит уточнить пару ситуаций, когда желательно разделение определения класса.
Классы разделяют при:
1. Объявление класса по отдельным файлам позволяет нескольким программистам одновременно работать над ним;
2. Вы можете добавить код в класс, не создавая исходный файл, который включает автоматически созданный источник. Visual Studio использует этот подход при создании форм Windows Forms, кода оболочки веб-службы и т.д. Можно создать код, который использует эти классы, без необходимости изменения файла, созданного в Visual Studio.
#ПолезноРешение задачи к посту.
void CompressByRLE(ReadOnlySpan<char> chars)
{
StringBuilder sb = new();
char separator = chars[0];
sbyte counter = 0;
foreach (char sym in chars)
{
if (sym == separator && counter != sbyte.MaxValue)
{
counter++;
continue;
}
sb.Append($"{counter}{separator}");
separator = sym;
counter = 1;
}
sb.Append($"{counter}{separator}");
ShowResult(chars.Length, ref sb);
}
void ShowResult(double sourceLength, ref StringBuilder sb)
{
Console.WriteLine($"Результат сжатия: {sb}");
Console.WriteLine($"Символов в сжатой последовательности: {sb.Length}");
Console.WriteLine($"Символов в исходной последовательности: {sourceLength}");
Console.WriteLine($"Данные были сжаты в ~{sourceLength / sb.Length} раз");
}
#Задача #Решение #Полезно🖥 Задача: сжатие данных без потерь (часть №1) (⭐️⭐️)
Написать метод, который принимает строку, сжимает её по принципу RLE и выводит результат на экран. Ниже приведены примеры.
CompressByRLE("WWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW");
👉 Результат сжатия: 9W3B24W1B14W
👉 Символов в сжатой последовательности: 12
👉 Символов в исходной последовательности: 51
👉 Данные были сжаты в ~4,25 раз
CompressByRLE("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
👉 Результат сжатия: 127A127A2A
👉 Символов в сжатой последовательности: 10
👉 Символов в исходной последовательности: 256
👉 Данные были сжаты в ~25,6 раз
CompressByRLE("SSSSOOOEEERROOOAAYYYYYDDDDOEUUUUUWWWWJJJORRUUUUUUUUUUXXXKHHHHHHMMMMMMGGGLLLLLLLJJJJ");
👉 Результат сжатия: 4S3O3E2R3O2A5Y4D1O1E5U4W3J1O2R10U3X1K6H6M3G7L4J
👉 Символов в сжатой последовательности: 47
👉 Символов в исходной последовательности: 83
👉 Данные были сжаты в ~1,7659574468085106 раз
Пишите варианты в комментариях. Решение будет сегодня вечером новым постом в канале.
Вторая часть этой задачи будет усложнена и продолжена следующим разом.
#Задача #Lvl2🖥 Рефлексия: атрибуты
Атрибуты представляют специальные инструменты, которые позволяют встраивать в сборку дополнительные метаданные. Они могут применяться как ко всему типу, так и к отдельным его частям (методу, свойству и т.д.). Основу атрибутов составляет класс Attribute, от которого образованы все остальные классы атрибутов. В .NET имеется множество встроенных классов атрибутов, а также мы можем создавать свои собственные классы атрибутов, которые будут определять метаданные других типов.
Допустим, нам надо проверять пользователя на соответствие некоторым возрастным ограничениям. Создадим свой атрибут, который будет хранить допустимый возрастной интервал и логику проверки:
public class AgeRangeValidationAttribute : Attribute
{
public int MinimalAge { get; init; }
public int MaximalAge { get; init; }
public AgeRangeValidationAttribute(int minimalAge, int maximalAge)
{
MinimalAge = minimalAge;
MaximalAge = maximalAge;
}
public bool IsValid(int age) => age >= MinimalAge && age <= MaximalAge;
}
Теперь напишем следующую иерархию классов и применим к ним атрибут:
[AgeRangeValidation(0, 9)]
public class Baby : User
{
public Baby(string name, int age, bool isMale) : base(name, age, isMale) { }
}
[AgeRangeValidation(10, 17)]
public class Teenager : User
{
public Teenager(string name, int age, bool isMale) : base(name, age, isMale) { }
}
[AgeRangeValidation(18, 120)]
public class Adult : User
{
public Adult(string name, int age, bool isMale) : base(name, age, isMale) { }
}
public abstract class User
{
public string Name { get; init; }
public int Age { get; init; }
public bool IsMale { get; init; }
public User(string name, int age, bool isMale)
{
Name = name;
Age = age;
IsMale = isMale;
}
public override string ToString() => $"[{Name}, {Age} years, {(IsMale ? "Male" : "Female")}]";
}
Каждый производный класс применяет атрибут, который указывается в квадратных скобках перед определением класса. Причём слово «Attribute» указывать необязательно. Обе записи [AgeRangeValidation] и [AgeRangeValidationAttribute] будут равноправны.
Если конструктор атрибута предусматривает использование параметров, то после имени атрибута мы можем указать значения для параметров конструктора, как у нас и происходит.
Добавим в базовый класс User метод проверки валидации возраста:
public bool Validate()
{
var type = GetType();
var ageRangeValidation = type.GetCustomAttributes<AgeRangeValidationAttribute>().FirstOrDefault();
// Проверка возрастного ограничения
if (ageRangeValidation != null) return ageRangeValidation.IsValid(Age);
return true;
}
Применим наработки:
Baby baby = new("Sam", 9, true);
Console.WriteLine($"{baby} Is {nameof(Baby)}: {baby.Validate()}");
Teenager teenager = new("Chloe", 21, false);
Console.WriteLine($"{teenager} Is {nameof(Teenager)}: {teenager.Validate()}");
Adult adult = new("Ilya", 25, true);
Console.WriteLine($"{adult} Is {nameof(Adult)}: {adult.Validate()}");
Проверим вывод:
[Sam, 9 years, Male] Is Baby: True [Chloe, 21 years, Female] Is Teenager: False [Ilya, 25 years, Male] Is Adult: TrueС помощью рефлексии мы определили корректность заполнения каждого класса, какими они и были задуманы. Однако это метаданные, которые описывают сам код, но не являются частью его логики. ➡️ Ограничение применения атрибута С помощью атрибута AttributeUsage можно ограничить их применение. Например, мы хотим, чтобы выше определённый атрибут мог применяться только к классам:
[AttributeUsage(AttributeTargets.Class)]
public class AgeRangeValidationAttribute : Attribute
{
//
}
Перечисление AttributeTargets имеет все возможные варианты. С помощью логической операции ИЛИ можно комбинировать эти значения. Например, чтобы атрибут мог применяться к классам и структурам: [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
#Полезно #ReflectionИз примера выше, что более вероятно будет отображено на экране?
🖥 Parallel LINQ: метод AsParallel
Мы знаем, что по умолчанию все элементы коллекции в LINQ обрабатываются последовательно. Можем убедиться в этом на примере вычисления количества чётных чисел:
var values = Enumerable.Range(1, 10);
var countOfEven = values.Count(x =>
{
Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Проверка {x}");
return (x & 1) == 0;
});
Повторяя данную процедуру сколь угодно раз, вывод в консоль всегда будет статичным:
[1] Проверка 1 [1] Проверка 2 [1] Проверка 3 [1] Проверка 4 [1] Проверка 5 [1] Проверка 6 [1] Проверка 7 [1] Проверка 8 [1] Проверка 9 [1] Проверка 10Взаимодействие с элементами происходит последовательно, как изначально и была заполнена коллекция. А идентификатор потока во всех случаях одинаков, что означает однопоточную работу с запросом. Однако существует класс ParallelEnumerable, который инкапсулирует функциональность Parallel LINQ (часто его ещё называют PLINQ) и позволяет выполнять обращения к коллекции в параллельном режиме. При обработке коллекции PLINQ использует возможности всех процессоров в системе. Источник данных разделяется на сегменты и каждый обрабатывается в отдельном потоке. Это позволяет произвести запрос на многоядерных машинах намного быстрее. В то же время, по умолчанию PLINQ выбирает последовательную обработку данных. Переход к параллельной обработке осуществляется в том случае, если это приведёт к ускорению работы. Как правило, при параллельных операциях возрастают дополнительные издержки. Поэтому если параллельная обработка потенциально требует больших затрат ресурсов, то PLINQ может выбрать последовательную обработку, если она, в свою очередь, не требует больших затрат ресурсов. ➡️ Метод AsParallel Метод позволяет распараллелить запрос к источнику данных. Он реализован как метод расширения LINQ у массивов и коллекций. При вызове данного метода источник данных разделяется на части (если это возможно) и над каждой частью отдельно производятся операции. Вернёмся к примеру выше и преобразуем запрос соответственно:
var countOfEven = values.AsParallel().Count(x =>
{
Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Проверка {x}");
return (x & 1) == 0;
});
Теперь же вывод будет непредсказуем с каждым разом, хаотичным и с разными потоками:
[9] Проверка 2 [1] Проверка 3 [1] Проверка 6 [6] Проверка 1 [9] Проверка 4 [10] Проверка 5 [1] Проверка 7 [6] Проверка 8 [9] Проверка 9 [10] Проверка 10Смысл применения PLINQ имеется преимущественно на больших коллекциях или при сложных операциях, где действительно выгода от распараллеливания запросов может перекрыть возникающие при этом издержки. Поэтому было бы более уместно, если бы наша коллекция была big data:
int countOfEven =
Enumerable.Range(1, 1_000_000)
.AsParallel()
.Count(x => (x & 1) == 0);
#Полезно #LINQ #Array #МногопоточностьРешение задачи к посту.
internal enum LevelGame
{
Easy,
Medium,
Hard
}
internal class GuessTheNumberGame
{
internal bool Victory { get; private set; }
private GuessTheNumberGame() { }
internal GuessTheNumberGame(LevelGame level, int limitTried)
{
this.limitTried = limitTried;
levelGame = level;
CreateNumber();
}
private readonly LevelGame levelGame;
private readonly int limitTried;
private int triedCounter;
private int secretNumber;
private int maxNumber;
internal void Play()
{
Console.WriteLine($"»»» Угадайте число от 1 до {maxNumber}! «««");
Interactive();
if (Victory) Console.WriteLine($"Вы угадали! Потребовалось попыток: {triedCounter}");
else Console.WriteLine($"Вы не угадали! Число было: {secretNumber}");
}
private void Interactive()
{
while (!Victory)
{
Console.WriteLine();
triedCounter++;
Console.Write("Введите Ваше число: ");
if (!int.TryParse(Console.ReadLine(), out int userValue))
{
Console.WriteLine("Ошибка ввода!");
continue;
}
if (userValue == secretNumber) Victory = true;
else if (triedCounter == limitTried) break;
else if (secretNumber < userValue) Console.WriteLine("Число меньше");
else Console.WriteLine("Число больше");
}
}
private void CreateNumber()
{
switch (levelGame)
{
case LevelGame.Easy:
maxNumber = 20;
break;
case LevelGame.Medium:
maxNumber = 50;
break;
case LevelGame.Hard:
maxNumber = 100;
break;
}
Random random = new Random();
secretNumber = random.Next(1, maxNumber + 1);
}
}
#Задача #Решение #Полезно🖥 Задача: угадай число (⭐️⭐️)
Написать класс, который реализует игру с пользователем. Суть игры заключается в угадывании игроком числа из определённого диапазона, которое было загадано игрой. Пользователь должен вводить в консоль число, а игра должна проверять его и выполнять свои задачи.
Необходимо реализовать три задачи в игре:
⏺Уровень сложности, где лёгкий генерирует число в диапазоне 1-20, средний в диапазоне 1-50 и сложный в диапазоне 1-100;
⏺Контроль количества выделенных попыток для угадывания;
⏺После каждой неудачной попытки игра должна давать подсказку — загаданное число меньше или больше.
Конструктор класса должен принимать уровень сложности и количество попыток. Ниже приведены примеры.
internal enum LevelGame
{
Easy,
Medium,
Hard
}
GuessTheNumberGame game = new GuessTheNumberGame(LevelGame.Easy, 8);
game.Play();
👉 »»» Угадайте число от 1 до 20! «««
👉
👉 Введите Ваше число: 10
👉 Число меньше
👉
👉 Введите Ваше число: 5
👉 Число меньше
👉
👉 Введите Ваше число: 3
👉 Вы угадали! Потребовалось попыток: 3
GuessTheNumberGame game = new GuessTheNumberGame(LevelGame.Medium, limitTried: 5);
game.Play();
👉 »»» Угадайте число от 1 до 50! «««
👉
👉 Введите Ваше число: 25
👉 Число больше
👉
👉 Введите Ваше число: 35
👉 Число больше
👉
👉 Введите Ваше число: йцу
👉 Ошибка ввода!
👉
👉 Введите Ваше число: 45
👉 Число меньше
👉
👉 Введите Ваше число: 40
👉 Вы не угадали! Число было: 43
GuessTheNumberGame game = new GuessTheNumberGame(LevelGame.Hard, 3);
game.Play();
👉 »»» Угадайте число от 1 до 100! «««
👉
👉 Введите Ваше число: -13
👉 Число больше
👉
👉 Введите Ваше число: 88
👉 Число больше
👉
👉 Введите Ваше число: 93
👉 Вы не угадали! Число было: 90
Пишите варианты в комментариях. Решение будет сегодня вечером новым постом в канале.
#Задача #Lvl2🖥 Рефлексия: разбор на примерах
В прошлой теме мы обсудили рефлексию, теперь же разберём её применение на практике.
➡️ Задача
Допустим, мы создаём библиотеку классов. В ней нам нужно реализовать класс, который будет создавать объекты неизвестного для себя типа.
В примерах ниже мы будем использовать класс User, который будет выступать в роли неизвестного объекта для нашей библиотеки:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public User() { }
public byte[] field;
public override string ToString() => $"{nameof(Id)}: {Id} | {nameof(Username)}: {Username}";
}
➡️ Первый пример рефлексии
Реализуем класс Creator в библиотеке и добавим в него единственный метод:
public class Creator<TProduct> where TProduct : class
{
public Creator() { }
public TProduct Factory()
{
// Получаем тип объекта
var type = typeof(TProduct);
// Получаем конструктор класса
var ctor = type.GetConstructors().FirstOrDefault();
// Вызываем конструктор и преобразуем результат в тип объекта
return ctor?.Invoke(null) as TProduct;
}
}
Используем его по назначению:
Creator<User> creator = new();
var user = creator.Factory();
Console.WriteLine(user); // Id: 0 | Username:
Мы реализовали метод Factory, чья задача создавать новый объект класса User, а для этого ему нужно получить конструктор класса. Потом мы просто вызываем его, как если бы мы написали new User().
Стоит учесть, что метод GetConstructors возвращает только публичные конструкторы, а в непосредственный вызов конструктора мы передаём null вместо аргументов. Этого достаточно на примере, но недостаточно на практике, т.к. конструкторы могут быть приватными и/или с набором аргументов, поэтому код должен грамотно проверять все моменты.
➡️ Второй пример рефлексии
Улучшим "фабрику", добавив перегрузку для метода Factory, чтобы создавать объект со значением в поле/свойстве:
public TProduct Factory(string fieldOrPropertyName, bool isField)
{
// Используем ранее реализованный метод
TProduct obj = Factory();
if (obj == null) return null;
var type = obj.GetType();
// Заполнение поля
if (isField)
{
// Получаем данные поля
var field = type.GetField(fieldOrPropertyName);
// Генерируем значение, определив тип поля
var value = GenerateValueByType(field.FieldType);
// Устанавливаем значение в поле
field.SetValue(obj, value);
}
// Заполнение свойства
else
{
// Получаем данные свойства
var prop = type.GetProperty(fieldOrPropertyName);
// Генерируем значение, определив тип свойства
var value = GenerateValueByType(prop.PropertyType);
// Устанавливаем значение в свойство
prop.SetValue(obj, value);
}
return obj;
}
private object GenerateValueByType(Type type)
{
if (type == typeof(int)) return 777;
if (type == typeof(string)) return "Lion";
if (type == typeof(byte[])) return new byte[] { 10, 15 };
// И т.д.
// ...
return null;
}
Проверим результат:
Creator<User> creator = new();
var user = creator.Factory(nameof(User.Username), false);
Console.WriteLine(user); // Id: 0 | Username: Lion
Первым параметром мы передаём имя поля/свойства (вспоминаем пользу nameof), в которое хотим установить значение. Вторым параметром помогаем методу понять с какими данными ему нужно работать — с полем или со свойством.
Здесь мы уже делаем проверку типов методом GenerateValueByType, чтобы случайно не установить, например, строку в целочисленный тип. Уже лучше, но всё ещё недостаточно.
➡️ Итог
Данные примеры слишком просты и рассчитаны исключительно для демонстрации. Во избежания ошибок, нужно внимательно проверять на: уровни доступа, возможность чтения/записи, конфликт присваиваемых типов и т.п.
Конкретно эту задачу было бы грамотнее реализовать интерфейсом, который бы принимался методами в качестве аргумента, а его реализация была на стороне потребителя. Однако сегодня мы изучаем рефлексию 🙂
#Полезно #ReflectionИз примера выше, что будет выведено на экран?
📈 Обогащаемся знаниями!
#Дайджест полезного материала за последнее время.
Фриланс советы:
⏺Cкрытые плюсы неофициального трудоустройства
Задачи:
⏺Парсинг логов
⏺Счастливый билет
⏺Конвертер римского числа
⏺Длиннейшая подстрока без повторяющихся символов
⏺Сортировка строк по количеству гласных
⏺Предложение с максимальным количеством слов
⏺Анализ частоты символов
⏺Проверка на анаграмму
Опросы — что будет выведено на экран:
⏺№1
⏺№2
⏺№3
⏺№4
⏺№5
⏺№6
⏺№7
Полезное:
⏺Работа сборщика мусора
⏺Знакомство с рефакторингом, разбор переименования переменных и методов
⏺Извлечение метода при рефакторинге
⏺Извлечение интерфейса при рефакторинге
⏺Упрощение условных выражений при рефакторинге
⏺ReadOnlyCollection не является неизменяемой
⏺Применение хеш-функции SHA-256 к строке
⏺Что такое Span<T>
⏺IDisposable, финализация объектов
⏺Unsafe code, разбор указателей
⏺Unsafe code, указатели на структуры, члены классов и массивы
⏺Знакомство с рефлексией
Самые важные хэштеги:
#Фриланс #Полезно #Задача #Опрос #LINQ #Проект
Решение задачи к посту.
bool IsAnagram(string firstText, string secondText)
{
var alphabet1 = ExtractAlphabetByText(firstText);
var alphabet2 = ExtractAlphabetByText(secondText);
return alphabet1.SequenceEqual(alphabet2);
}
ReadOnlySpan<char> ExtractAlphabetByText(string text) => text
.ToLower()
.Where(char.IsLetter)
.OrderBy(x => x)
.ToArray();
#Задача #Решение #Полезно #LINQ🖥 Задача: проверка на анаграмму (⭐️)
Написать метод, который принимает две строки, проверяет их на анаграмму и возвращает результат. Ниже приведены примеры.
IsAnagram("listen", "Silent!");
👉 True
IsAnagram("Shutter Island", "Truths and Lies");
👉 True
IsAnagram("Привет, мир", "Hello, world");
👉 False
IsAnagram("Edward Daniels", "Andrew Laeddis");
👉 True
IsAnagram("Rachel Solando", "Dolores Chanal");
👉 True
IsAnagram("Дождь", "Дрожь");
👉 False
Пишите варианты в комментариях. Решение будет сегодня вечером новым постом в канале.
#Задача #Lvl1🖥 Что такое рефлексия?
Рефлексия представляет собой процесс выявления типов во время выполнения приложения. Каждое приложение содержит набор используемых классов, интерфейсов, а также их методов, свойств и прочих кирпичиков, из которых складывается приложение. Рефлексия как раз и позволяет определить все эти составные элементы приложения. То есть основная задача рефлексии — это исследование типов. Основной функционал сосредоточен в пространстве имён System.Reflection.
Основные классы в пространстве имён System.Reflection:
⏺Assembly — представляет сборку и позволяет манипулировать этой сборкой;
⏺AssemblyName — хранит информацию о сборке;
⏺MemberInfo — абстрактный базовый класс для EventInfo, FieldInfo, MethodInfo и PropertyInfo;
⏺EventInfo — хранит информацию о событии;
⏺FieldInfo — хранит информацию о поле типа;
⏺MethodInfo — хранит информацию о методе;
⏺PropertyInfo — хранит информацию о свойстве;
⏺ConstructorInfo — класс, представляющий конструктор;
⏺Module — позволяет получить доступ к определённому модулю внутри сборки;
⏺ParameterInfo — хранит информацию о параметре метода.
Эти классы представляют составные блоки типа и приложения: методы, свойства и т.д. Но чтобы получить информацию о членах типа, нам надо воспользоваться классом Type.
Класс Type представляет изучаемый тип, инкапсулируя всю информацию о нём. С помощью его свойств и методов можно получить эту информацию.
➡️ Получение типа
Чтобы управлять типом и получать всю информацию о нём, нужно сперва получить данный тип. Это можно сделать с помощью оператора typeof:
Type type = typeof(Person);
Console.WriteLine(type); // Person
public class Person
{
public string Name { get; }
public Person(string name) => Name = name;
}
Также можно получить тип с помощью метода GetType, унаследованного от класса Object:
Person person = new Person("Ilya");
Type type = person.GetType();
Однако здесь, чтобы получить тип, надо создавать объект класса, что не всегда может быть уместным.
➡️ Получение всех компонентов типа
Метод GetMembers возвращает все доступные компоненты типа в виде объекта MemberInfo. Этот объект позволяет извлечь некоторую информацию о компоненте типа.
Применим метод и выведем все доступные элементы типа:
foreach (MemberInfo member in typeof(Person).GetMembers())
{
Console.WriteLine($"{member.DeclaringType} {member.MemberType} {member.Name}");
}
public class Person
{
public int Age { get; set; }
public Person(string name, int age)
{
this.name = name;
Age = age;
}
private string name;
public void Print() => Console.WriteLine($"Name: {name} Age: {Age}");
}
Мы получим все общедоступные члены класса Person:
Person Method get_Age Person Method set_Age Person Method Print System.Object Method GetType System.Object Method ToString System.Object Method Equals System.Object Method GetHashCode Person Constructor .ctor Person Property AgeОбратим внимание, что в данном случае мы получаем только все публичные компоненты класса и нам не выводится информация о приватной переменной name. #Полезно #Reflection
Из примера выше, что будет выведено на экран?
