Fluent Interface is an advanced API design that really on extension methods and methods chaining. When of the most famous examples of fluent API is the linq extension methods for IEnumerable.
var passed=Students.Where(s=>s.Passed).Select(s=>s.Name).ToList();
In the asynchronous Programming, async/await, fluent APIs are kind of limited due to the nature of the Task object that it is hot build default, in this article, we will overcome this problem and will build a small library that allows fluent extension methods.
as an example,
var data=await GetDataAsync()
.AsRepeatable(TimeSpan.FromSeconds(10))
.FirstOrDefaultAsync(data=>data.Any());
another example could be
var data=await GetNewsAsync()
.Where(newsList=>newsList.Date>=DateTime.Today)
.SelectMany(newsList=>newsList.NewsItems)
.ToList();
Just to give you an idea of how this code could be written using Imperative approach
var newsList=await GetNewsAsync();
if(newsList.Date>=DateTime.Today)
{
return newsList.Select(news=>news.NewsItems).ToList();
}
else
return new List<NewsItem>();
It is up to you to choose which code fashion you would like, if you like the second one, you can stop reading this article as in the rest of the article we will extend the c sharp language to make the first one applicable.
Good you decided to continue that show that you are a very elegant software engineer so please grape your coffee and you can invite me on as well using
Buy AhmedFouad a Coffee. ko-fi.com/ahmedfouad
Step 1: Convert your async Task to Task>
This will help us to get rid of the NullReferenceExceptions and concentrate only on the extension methods.
public static Func<Task<Option<T>>> ToOption<T>(this Func<Task<T>> context)
{
return new Func<Task<Option<T>>>(async () =>{
try
{
var result = await context.Invoke();
return result.SomeNotNull();
}
catch (Exception e)
{
return Option.None<T>();
}});
}
This method simply invokes the async method and return Option.None when the value is null or exception happened or the value as some when there is a value.
we will need to install the option type nugget package
Step 2: Build a Wrapper class in which we will implement our extension methods
public class ExtendedTask<T>
{
private readonly Func<Task<Option<T>>> \_taskOption;
public ExtendedTask(Func<Task<T>> task)
{
\_taskOption = task.ToOption();
}
public ExtendedTask(Func<Task<Option<T>>> task)
{
\_taskOption = task;
}
}
form here it is very easy to implement our extension methods, we will just delegate the implementation to the option type.
public ExtendedTask<T> Where(Func<T,bool> condition)
{
var conditionedTask = \_taskOption.Select(func => new Func<Task<Option<T>>>(async () =>
{
var result = await func.Invoke();
return result.Where(condition);
}));
return new ExtendedTask<T>(conditionedTask);
}
It will return the new ExtendedTask of value when the condition is meet and ExtendedTask of None otherwise.
Now we will implement the select method and select async method using the same strategy.
public ExtendedTask<TP> Select<TP>(Func<T,TP> selector)
{
var conditionedTask = \_taskOption.Select(func => new
Func<Task<Option<TP>>>(async () =>
{
var result = await func.Invoke();
return result.Select(selector);
}));
return new ExtendedTask<TP>(conditionedTask);
}
public ExtendedTask<TP> SelectAsync<TP>(Func<T, Task<TP>> selector)
{
var conditionedTask = \_taskOption.Select(func => new
Func<Task<Option<TP>>>(async () =>
{
var result = await func.Invoke();
var [@select](http://twitter.com/select) = await result.Select(selector.Invoke)
.ValueOr(()=>Task.FromResult(default(TP)));
return [@select](http://twitter.com/select).SomeWhen(p =>
{
var b = p.Equals(default(TP));
return !b;
});
}));
Step 3: Implement the AsTask() method
public async Task<T> AsTask()
{
return (await \_taskOption.Select(async task => await
task())).ValueOrDefault();
}
This method simply returns a new task that executes the decorated task and will simply return the default of T when the value is None.
Step4: Implement AsRepeatable() method
As the repeatable method is a transformation method that transforms a scalar Task to a Vector IAsyncEnumerable
public IAsyncEnumerable<T> AsRepeatable()
{
return new TaskAsyncEnumerable<T>( \_taskOption);
}
public IAsyncEnumerable<T> AsRepeatable(TimeSpan timeSpan)
{
return new TaskAsyncEnumerable<T>(\_taskOption ,timeSpan);
}
And here is the Implementation of TaskAsyncEnumerable
It is quite straight forward The enumerator will keep executing the task on iterating and will only stop when the Iterating stop or when the value becomes null.
Step 5: Let’s try it
var getDataAsync = new ExtendedTask<News>(GetDataAsync);
var data = await getDataAsync
.AsRepeatable(TimeSpan.FromSeconds(1))
.FirstOrDefaultAsync(data => data.Items.Any());
Step 6
If you like the article please share it, follow me on medium and on twitter to get my upcoming
and pay me the coffee at
Top comments (0)