Возврат кортежа из метода
Адекватно использовать так кортежи (возвращать из метода GetProduct()) или для этого делать отдельный класс ?
public static class WarehouseGoods
{
private static object productsLock = new object();
private static List<Product> products = new List<Product>();
public static void AddProduct(Product product)
{
lock (productsLock)
{
products.Add(product);
}
}
public static (bool success,Product product) GetProduct()
{
Product? product = null;
lock (productsLock)
{
if (products.Count is not 0)
{
product = products.ElementAt(0);
products.RemoveAt(0);
return (true,product);
}
else
{
return (false, product);
}
}
}
}
Далее я просто проверяю bool, а в случае True использую Product
var result = WarehouseGoods.GetProduct();
if (result.success)
{
CheckProduct(result.product);
}
Или можно просто проверять product на null xD
Как правильней то ?
Ответы (3 шт):
В вашем случае следует обратить внимание на семантику Try - методов вроде Int32.TryParse.
public static bool TryGetProduct(out Product product)
{
lock (productsLock)
{
if (products.Count is not 0)
{
product = products.ElementAt(0);
products.RemoveAt(0);
return true;
}
else
{
product = null;
return false;
}
}
}
В таком случае вы сможете возвращать и значение, обозначающее, удалась ли попытка, и опциональный результат с помощью out-параметра в форме общепринятой конвенции.
И я бы еще вот эту строку поменял:
products.Count > 0
Как по мне, отсутствие паттерн матчинга здесь выглядит логичнее, чем присутствие.
Судя по логике, это похоже на потокобезопасную очередь, ну тогда и используйте потокобезопасную очередь.
Статические классы не рекомендую использовать, могут возникнуть проблемы при масштабировании и контроле времени жизни объектов. В таких случаях лучше использовать синглтон. Тогда даже когда вы доживете до внедрения IoC паттерна в проект, будете рады, что у вас не статика.
public class WarehouseGoods
{
private static readonly Lazy<WarehouseGoods> _instance = new(() => new WarehouseGoods());
public static WarehouseGoods Instance => _instance.Value;
private readonly ConcurrentQueue<Product> products = new();
private WarehouseGoods() { } // прячу конструктор
public void AddProduct(Product product)
{
products.Enqueue(product);
}
public bool GetProduct(out Product product)
{
return products.TryDequeue(out product);
}
}
Использовать его просто
WarehouseGoods.Instance.AddProduct(product);
if (WarehouseGoods.Instance.GetProduct(out Product product))
{
Use(product); // какая-то работа с продуктом
}
Решение дал, а про кортежи ничего не сказал. Так вот, кортеж - это лековесная анонимная (то есть безымянная) структура данных. Кортеж следует использовать тогда, когда нужно вернуть несколько сгруппированных значений для комплексной их обработки.
Например
public (int, int) GetLocation()
{
// ...
return (x, y);
}
Получение результата будет выглядеть так
(int x, int y) = GetLocation();
Данный кортеж сразу декомпозируется и используются данные, полученные из метода. Удобно.
Ваш случай немного другой, один аргумент показывает успешность результата, то есть грубо говоря валидность второго аргумента, то есть, эти результаты семантически не являются логически единой структурой, а следовательно и в кортеж их объединять будет не очень логично.
В данном случае подойдет другой способ, показанный выше, а практика его применения (внезапно) исходит из логики обработки исключений, которую я рассматривал здесь. Такая практика обширно применяется в .NET, и знакома почти всем C# разработчикам (int.TryParse, Dictionary.TryGetValue и т.д.), а значит ваш код будет легко читаться, а поведение метода будет выглядеть предсказуемо.
Начиная с C# 8 существуют Nullable reference types. Можно их использовать как вариант возврата.
Сигнатура метода будет:
Product? GetProduct()
В данном случае мы явно указываем, что результатом может быть null, что это не случайная ошибка, когда забыли что-то инициализировать.
Использование:
var result = Warehouse.GetProduct();
if (result != null)
...
Примером могут служить такие методы LINQ, как FirstOrDefault и LastOrDefault - они тоже возвращают nullable значение для ссылочных типов. Только они не удаляют этот элемент.
Вот этот код неэффективен:
product = products.ElementAt(0);
products.RemoveAt(0);
Он берёт самый первый элемент и удаляет его, после чего все остальные элементы сдвигаются на один вперёд. Если в коллекции много элементов, то времени на это тратится много.
Лучше брать и удалять элемент с конца. Конечно, если это допустимо логикой программы.
int index = products.Count - 1;
product = products[index];
products.RemoveAt(index);
Это не будет двигать остальные элементы.