Может ли иметь смысл менять ICollection на IEnumerable?
У меня имеется класс Api объекта, в котором можно сделать запрос к серверу, и получить от него данные.
Класс имеет 2 метода, как асинхронное получение данных, так и синхронное (вызывает асинхронный метод путем получения объекта ожидания, и возвращает результат). Так же присутствуют 2 иветна, которые вызываются при ошибке, а так же при успешном завершении операции (все это описано в базовом классе):
internal abstract class BaseApiObject<TType, TOptions>
: IApiObject<TType, TOptions>
where TType : IDataObject
where TOptions : IOptions
{
protected readonly Client Client;
protected BaseApiObject(Client client)
{
Client = client;
}
public event EventHandler<ICompleteArgs<TType>> Complete;
public event EventHandler<IError> Error;
public ICollection<TType> Get(TOptions options)
{
return GetAsync(options, CancellationToken.None)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
}
public abstract Task<ICollection<TType>>
GetAsync(TOptions options, CancellationToken cancellationToken = default);
protected Uri RequestUri(TOptions options)
{
return new Uri(Client.Options.BaseUri, options.Prepare(Client.Options.Token));
}
protected virtual void OnComplete(ICompleteArgs<TType> e)
{
Complete?.Invoke(this, e);
}
protected virtual void OnError(IError e)
{
Error?.Invoke(this, e);
}
}
Так же имеется класс наследник, который должен возвращать коллекцию данныъ при успешном завершении, и эта коллекция имеет тип ICollection<T>, т.к. я делаю несколько Linq запросов в коде, я подумал, а может ли иметь смысл возвращать IEnumerable<T> а не делать создавать коллекцию, и кидать в нее данные:
internal sealed class Years
: BaseApiObject<IYear, IYearsOptions>, IYears
{
internal Years(Client client)
: base(client)
{
}
public override async Task<ICollection<IYear>>
GetAsync(IYearsOptions options, CancellationToken cancellationToken = default)
{
if (options is null)
{
throw new NullReferenceException(string.Format(Errors.MustBeNotNull, nameof(options)));
}
using (HttpRequestMessage requestMessage
= new HttpRequestMessage(HttpMethod.Get, RequestUri(options)))
{
using (HttpResponseMessage responseMessage
= await Client.HttpClient
.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken))
{
string contentString = await responseMessage.Content.ReadAsStringAsync();
JObject jObject = JObject.Parse(contentString);
if (!responseMessage.IsSuccessStatusCode
&& responseMessage.StatusCode == HttpStatusCode.InternalServerError)
{
JToken errorToken = jObject[JsonFields.Error];
if (errorToken is null)
{
OnError(new Error(Errors.Internal));
throw new ApplicationException(Errors.Internal);
}
string errorMessage = errorToken.ToObject<string>();
OnError(new Error(errorMessage));
throw new OperationCanceledException(errorMessage);
}
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
YearCompleteArgs completeArgs
= JsonConvert.DeserializeObject<YearCompleteArgs>(contentString);
JToken resultsToken = jObject[JsonFields.Results];
if (resultsToken is null)
{
OnError(new Error(Errors.Internal));
throw new ApplicationException(Errors.Internal);
}
ICollection<IYear> responseCollection = new List<IYear>(resultsToken.Count());
IEnumerable<KodikYear> result =
from kodikYear
in resultsToken
select kodikYear.ToObject<KodikYear>();
foreach (KodikYear kodikYear in result)
{
responseCollection.Add(kodikYear);
}
completeArgs.Results = responseCollection;
OnComplete(completeArgs);
return responseCollection;
}
}
}
}
Код который меня беспокоит, это:
ICollection<IYear> responseCollection = new List<IYear>(resultsToken.Count());
IEnumerable<KodikYear> result =
from kodikYear
in resultsToken
select kodikYear.ToObject<KodikYear>();
foreach (KodikYear kodikYear in result)
{
responseCollection.Add(kodikYear);
}
Я вот думаю, это может ускорить формирование ответа за счет отдачи перечислителя, вместо самой коллекции, и еще даст ли это плюс, если допустим сделается сразу 2 запроса, один из которых будет идти сразу за другим?
(понятное дело что там можно сделать просто вызов ToList, но я хочу добавить прерывание пополнения коллекции при отмене операции)
Ответы (1 шт):
Вы не предостерегаете наследников вашего класса BaseApiObject от 1 проблемы: редактируемость коллекции. Если запрос идёт из условного интернета, то возникает большой вопрос: зачем нам нужна изменяемость, если она никак на данные с сервера не влияет? Поэтому лучше возвращать IEnumerable<T>, а ещё лучше — IAsyncEnumerable<T> (оператор yield также не повредит).
Также лучше убрать синхронную версию метода GetAsync — Get: вы за пользователя выбираете, как вернуть результат в синхронном виде. Если реализация вашего метода изменится, то к чёртовой бабушке может полететь код всех пользователей вашего метода, т.е. пользователи метода зависят от её реализации. Результат-то тот же, да вот реализация другая, и проблемы другие, несмотря на результат, которые выпячиваются наружу (например, dead-lock).
Поэтому вам никогда не стоит делать методы, реализацию которых вы не можете изменить. Это тоже самое, что не использовать интерфейсы в коде на 1 млн. строк.