I was curious if I could enhance the standard generic List<>
functionality because I was fed up with writing code such as:
var list = new List<Person>{
new Person{Name = "David", Age = 24},
new Person{Name = "John", Age = 30}
};
list.Add(new Person{Name = "Terry", Age = 28});
I'd prefer that T
could be implicitly constructed. The best I came up with allowed me to do this with up to four object construction parameters:
var list = new ListWithConstructor<string, int, Person>(
(name,age) => new Person { Name = name, Age = age })
{
{"David", 24},
{"John", 30}
};
list.Add("Terry", 28);
This is implemented like this:
public class ListWithConstructor<T1, T> : List<T>
{
private readonly Func<T1, T> itemConstructor;
public ListWithConstructor(Func<T1, T> itemConstructor)
{
this.itemConstructor = itemConstructor;
}
public void Add(T1 arg1)
{
base.Add(itemConstructor(arg1));
}
}
public class ListWithConstructor<T1, T2, T> : List<T>
{
private readonly Func<T1, T2, T> itemConstructor;
public ListWithConstructor(Func<T1, T2, T> itemConstructor)
{
开发者_Python百科 this.itemConstructor = itemConstructor;
}
public void Add(T1 arg1, T2 arg2)
{
base.Add(itemConstructor(arg1, arg2));
}
}
...and so on for up to four arguments.
Obviously the other List<>
constructors (taking a capacity and an IEnumerable
of existing elements) can be implemented as well.
How can this be improved?
The functionality that you are looking for is (almost) already available at your fingertips:
var names = new[] { "David", "John" };
var persons = new List<Person>(names.Select(name => new Person { Name = name }));
This way you also have a clear separation of concerns; the List
is not at all involved in the object construction (not even by just invoking a delegate), but is simply assigned a sequence of objects. The transformation is taken care of separately.
This can also handle the case of transforming multiple values into single objects:
public static IList<Person> GetListFromNamesAndAges(string[] names, int[] ages)
{
if (names.Length != ages.Length)
{
throw new ArgumentException("names and ages must be of equal length.");
}
return new List<Person>(
names.Select((name, index) =>
new Person { Name = name, Age = ages[index] }));
}
// usage example:
var persons = GetListFromNamesAndAges(
new[] {"David", "John"},
new[] {24, 30});
In the case of merging values from exactly two lists into single objects, using Zip
might give slightly cleaner code;
return names
.Zip(ages, (name, age) => new Person {Name = name, Age = age})
.ToList();
You shouldn't do it like this. You are violating the "Separation Of Concerns" principle.
Why don't you just declare an implicit conversion from string
to Person
for the case with one constructor parameter?
public class Person
{
public Person(string name)
{
Name = name;
}
public string Name { get; set; }
public static implicit operator Person(string name)
{
return new Person(name);
}
}
var list = new List<Person>();
list.Add("Terry");
To be able to shorten the code for adding an object with multiple constructor parameters, I think you should do the following:
Use the Func
you created directly in the call to Add
:
Func<string, string, Person> ctor =
(firstName, name) => new Person { Name = name, FirstName = firstName };
list.Add(ctor("Terry", "McDouglas"));
list.Add(ctor("Jones", "Miller"));
This is a lot cleaner.
精彩评论