Reputation: 101
This is My Class which is basically Closed Coupled To Type I want to Generalise this Class.
So I can use with all the Class
one thing is common that there will be a Name field while invoking this method
public class CustomSuggestionProvider : ISuggestionProvider
{
private const int batchSize = 30;
private string _criteria = string.Empty;
private int _skipCount;
private readonly ObservableCollection<LanguageItem> _observableCollection;
private readonly List<LanguageItem> _source;
public CustomSuggestionProvider(ObservableCollection<LanguageItem> observableCollection, List<LanguageItem> source)
{
_observableCollection = observableCollection;
_source = source;
}
public bool HasMoreSuggestions { get; private set; } = true;
public Task<IList<object>> GetSuggestionsAsync(string criteria, CancellationToken cancellationToken)
{
_criteria = criteria;
var newItems = _source.Where(x => x.Name.IndexOf(_criteria, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
if (cancellationToken.IsCancellationRequested)
return null;
HasMoreSuggestions = newItems.Count > batchSize;
_skipCount = batchSize;
return Task.FromResult<IList<object>>(newItems.Take(batchSize).Cast<object>().ToList());
}
}
I Just made it Generalised like this.
Made all the closely coupled class to Type T.
Kindly have a look.
public class CustomSuggestionProvider<T> : ISuggestionProvider
{
private const int batchSize = 30;
private string _criteria = string.Empty;
private int _skipCount;
private readonly ObservableCollection<T> _observableCollection;
private readonly List<T> _source;
public CustomSuggestionProvider(ObservableCollection<T> observableCollection, List<T> source)
{
_observableCollection = observableCollection;
_source = source;
}
public bool HasMoreSuggestions { get; private set; } = true;
]
public Task<IList<object>> GetSuggestionsAsync(string criteria, CancellationToken cancellationToken)
{
_criteria = criteria;
var newItems = _source.Where(x =>Name.IndexOf(_criteria, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
if (cancellationToken.IsCancellationRequested)
return null;
HasMoreSuggestions = newItems.Count > batchSize;
_skipCount = batchSize;
return Task.FromResult<IList<object>>(newItems.Take(batchSize).Cast<object>().ToList());
}
public Task<IList<object>> GetSuggestionsAsync(CancellationToken cancellationToken)
{
var newItems = _source.Where(x => x.Name.StartsWith(_criteria)).Skip(_skipCount).ToList();
if (cancellationToken.IsCancellationRequested)
return null;
HasMoreSuggestions = newItems.Count > batchSize;
_skipCount += batchSize;
return Task.FromResult<IList<object>>(newItems.Take(batchSize).Where(x => !_observableCollection.Any(y => y.Id == x.Id)).Cast<object>().ToList());
}
}
I just stuck when at this statement
The 'Name' does not exist here
var newItems = _source.Where(x => x.Name.IndexOf(_criteria, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
Can you please help troubleshoot this.
Thanks in Advance.
Upvotes: 1
Views: 91
Reputation: 81523
Assuming every type you want to use has a Name
property, you would just need an interface
and a constraint:
Constraints inform the compiler about the capabilities a type argument must have. Without any constraints, the type argument could be any type. The compiler can only assume the members of System.Object, which is the ultimate base class for any .NET type. For more information, see Why use constraints. If client code uses a type that doesn't satisfy a constraint, the compiler issues an error. Constraints are specified by using the where contextual keyword
Given:
public interface IName
{
public string Name { get; set; }
}
You would put this interface on every type you want to use as the generic parameter to your class:
public class Bob : IName
{
...
}
Example
Then just add the constraint to the class with where
:
public class CustomSuggestionProvider<T> : ISuggestionProvider
where T: IName
{
}
Note there are other options if you can't constrain the generic type, either using Func<T,bool>
or an expression depending on the use case.
Upvotes: 5
Reputation: 30474
There are several methods that you could use. Which one you use depends on how generic you want your class to be.
var newItems = _source.Where(x => x.Name...
Here every x
is an object of Type T. Apparently you plan to use your generic class only for T
classes that have a property Name
. You'll have to promise the compiler that you will only use T
that have this property.
interface IHasName
{
string Name {get; }
}
public class CustomSuggestionProvider<T> : ISuggestionProvider
where T : IHasName
{
...
Usage:
class MyClass : IHasName {...}
var provider = new CustomSuggestionProvider<MyClass>(...);
The disadvantage is that every class that you want to use for your CustomSuggestionProvider has to implement this interface.
Another Disadvantage of the interface method is that it only works for property Name
. Suppose you want this for a class with a LastName
, or maybe with an integer Id
?
You want to use the Name
in a Where
. You select a string property from your T
, and compare it with string _criteria
. Why not tell your class which property it should select? To find out how to select a property, see Enumerable.Select
class CustomSuggestionProvider<T> : ...
{
public Func<TSource,string> ItemSelector {get; set;} t => t.ToString();
...
private IEnumerable<T> NewItems => this._source
.Where(t => this.ItemSelector(t)
.IndexOf(_criteria, StringComparison.OrdinalIgnoreCase) >= 0))
.Skip(_skipCount)
.ToList();
public Task<IList<object>> GetSuggestionsAsync(
string criteria,
CancellationToken cancellationToken)
{
_criteria = criteria;
return this.GetSuggestionsAsync(cancellationToken);
}
public Task<IList<object>> GetSuggestionsAsync(CancellationToken cancellationToken)
{
var newItems = this.NewItems;
...
return Task.FromResult<IList<object>>(newItems.Take(batchSize).Where(t => ...
}
Apparently the overloads of GetSuggestionAsync are very similar, so I took the freedom to let one call the other.
If you omit to set the ItemSelector a default value is used.
Usage:
var customSuggestionProvider = new CustomSuggestionProvider<MySuggestion>(...)
{
ItemSelector = t => t.Name,
};
Or:
var customerSuggester = new CustomSuggestionProvider<Customer>(...)
{
ItemSelector = customer => customer.City,
};
By the way, why do you ToList
all your NewItems, if you only use the first batchSize
ones? So skip the first 5 T objects, put the remaining 995 in a list and use only the first 10 of them (= indexes 5..14). Consider to remove the ToList
part.
If you want, you can add ItemSelector to your constructor. I'm not sure if using constructors with a lot of properties will make your code more readable. For years there is a tendency to make constructors smaller and provide properties to set the values that you would otherwise have to set in a constructor. Provide obvious default values for these properties. You already do something like this with _criteria
.
Upvotes: 1
Reputation: 117124
By introducing a generic type T
you are saying that you will know what the type is when you call the method. So therefore, you also know what property you are trying to run the criteria against. It's easy to then just simply introduce a Func<T, string> getName
delegate.
Now your first method looks like this:
public Task<IList<object>> GetSuggestionsAsync(string criteria, Func<T, string> getName, CancellationToken cancellationToken)
{
_criteria = criteria;
var newItems = _source.Where(x => getName(x).IndexOf(_criteria, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
if (cancellationToken.IsCancellationRequested)
return null;
HasMoreSuggestions = newItems.Count > batchSize;
_skipCount = batchSize;
return Task.FromResult<IList<object>>(newItems.Take(batchSize).Cast<object>().ToList());
}
THere seems to be a lot of work here to produce batches of items that you aren't actually doing anything async
with to get. You might as well use IEnumerable<IEnumerable<T>>
instead of repeatedly calling Task<IList<object>>
.
To do this, start with this extension method:
public static IEnumerable<IEnumerable<T>> Buffer<T>(this IEnumerable<T> source, int size)
{
IEnumerable<T> GetBuffer(IEnumerator<T> enumerator)
{
var i = 0;
do
{
yield return enumerator.Current;
} while (++i < size && enumerator.MoveNext());
}
var e = source.GetEnumerator();
while (e.MoveNext())
{
yield return GetBuffer(e);
}
}
Then you can write this:
public IEnumerable<IEnumerable<T>> GetSuggestions(string criteria, Func<T, string> getName) =>
_source
.Where(x => getName(x).IndexOf(criteria, StringComparison.OrdinalIgnoreCase) >= 0)
.Buffer(batchSize);
Much simpler and possibly even more performant.
Upvotes: 0