How get an prop of Object key in a Dictionary C#? - c#

I have this homework, I have only 1 problem, and I don't know the solution. We have this class, and we musn't create another variables or methods...
I have a beers dictionary with < Beer object, int income >. But the method has got only the Beer object's name (prop), not the object.
And I don't have another idea, how can I get the Beer object's name from a Dictionary
I have only 2 idea, but these don't work.
The first is I tried use a ContainsKey() method. The second is an foreach iteration
using System;
using System.Collections.Generic;
namespace PubBeer
{
public class Beer
{
string name;
int price;
double alcohol;
public string Name{ get { return name; } }
public int Price{ get; set; }
public double Alcohol{ get { return alcohol;} }
public Sör(string name, int price, double alcohol)
{
this.name= name;
this.price= price;
this.alcohol= alcohol;
}
public override bool Equals(object obj)
{
if (obj is Beer)
{
Beer other = (Beer)obj;
return this.name== other.name;
}
return false;
}
}
public class Pub
{
int income;
IDictionary<Beer, int> beers= new Dictionary<Beer, int>();
public int Income{ get; set; }
public int Sold(string beerName, int mug)
{
// Here the problem
beers; // Here I want to like this: beers.Contains(beerName)
// beers.ContainsKey(Object.Name==beerName) or someone like this
// foreach (var item in beers)
// {
// item.Key.Name== beerName;
// }
}
...

Use LINQ to query over the collection of Keys.
//Throws an error if none or more than one object has the same name.
var beer = beers.Keys.Single(b => b.Name == beerName);
beers[beer] = ...;
// -or -
//Selects the first of many objects that have the same name.
//Exception if there aren't any matches.
var beer = beers.Keys.First(b => b.Name == beerName);
beers[beer] = ...;
// -or -
//Selects the first or default of many objects.
var beer = beers.Keys.FirstOrDefault(b => b.Name == beerName);
//You'll need to null check
if (beer != null)
{
beers[beer] = ...;
}
// etc...
Update: NON-LINQ Alternative
Beer myBeer;
foreach (var beer in beers.Keys)
{
if (beer.Name == beerName)
{
myBeer = beer;
break;
}
}
if (myBeer != null)
{
beers[myBeer] = ...;
}

You could use Any() on the Keys collection:
if (beers.Keys.Any(x => x.Name == beerName))
{
}
In the worst case this would have to look through all beers though - if you usually look up beers by name you should consider making the beer name the key and the beer object itself the value in the dictionary.
Once you have identified that such a beer exists you can use First() to select it:
Beer myBeer = beers.First(x => x.Key.Name == beerName).Key;

try to use Keys property
beers.Keys.Where(p => p.name == beername )
or
beers.Keys.FirstOrDefault(p => p.name == beername)

Related

List.Sort by property name - Including subclasses

I have a bit of a challenge. I need to sort a List of objects, and I need to sort it from a string representing the path to the property in any sub class.
I need to use the List.Sort() and not OrderBy().
Lest make a simple example. I have a list of persons represented by two sub classes for identification and name
public class NameParts
{
public String FirstName { get; set; }
public String LastName { get; set; }
}
public class Identification
{
public String NiNumber { get; set; }
public NameParts Name { get; set; }
}
public class Person
{
public String Email { get; set; }
public String Phone { get; set; }
public Int16 Age { get; set; }
public Identification Id { get; set; }
}
Now I need to sort the list by age. Very simple
public static void SortByAge(List<Person> listToSort)
{
listToSort.Sort((x, y) => x.Age.CompareTo(y.Age));
}
And even by NiNumber and FirstName it is fairly simple this way
public static void SortByNiNumber(List<Person> listToSort)
{
listToSort.Sort((x, y) => x.Id.NiNumber.CompareTo(y.Id.NiNumber));
}
public static void SortByFirstName(List<Person> listToSort)
{
listToSort.Sort((x, y) => x.Id.Name.FirstName.CompareTo(y.Id.Name.FirstName));
}
Now comes the tricky part. I need to perform all the above sorts giving a string that represents the path to theproperty to sort by.
Like "Id.Name.FirstName"
So I need
public static void SortByAny(List<Person> listToSort, String sortBy)
{
//??????
}
That can be called with
List<Person> theList = new List<Person>();
SortByAny(theList, "Age");
SortByAny(theList, "Id.NiNumber");
SortByAny(theList, "Id.Name.FirstName");
I know I need to use reflection for this, and I have managed to do so but I cannot get further than properties in the Person Class itself, so I probably need to do something else, and this is where I'm stuck.
Does anyone have some brilliant ideas on how to solve this?
Thanks
You can modify the approach #E.Mourits linked: C# dynamic. String property path.
I included a bit of error checking, on error you have to check the InnerException of the InvalidOperationException the Sort method can throw.
static void SortByAny<T>(List<T> list, string path)
{
list.Sort((x, y) => ReflectOnPath(x, path).CompareTo(ReflectOnPath(y, path)));
}
static IComparable ReflectOnPath(object o, string path)
{
object value = o;
var pathComponents = path.Split('.');
foreach (var component in pathComponents)
{
if (value == null)
{
throw new NullReferenceException($"Path '{path}' can not be resolved at: {component}.");
}
var prop = value.GetType().GetProperty(component);
if (prop == null)
{
throw new ArgumentException($"Path '{path}' can not be resolved at: {component}.", nameof(path));
}
value = prop.GetValue(value, null);
}
if (!(value is IComparable))
{
throw new ArgumentException($"Value at path '{path}' does not implement ICompareable.", nameof(path));
}
return (IComparable)value;
}
If some of the values you want to compare do not implement IComparable you have to add more details how you want to compare them in this case.
Do you really need to put reflection in place? If u need to sort the list by a specific and determined number of "paths" i would suggest you implementing it in a fixed way, going straight ahead with sorting. maybe a switch might help?
switch(sortByString){
case: "Id.NiNumber":
SortByNiNumber(List<Person> listToSort);
break;
...
}
if you don't have too many options it would be faster. And maybe you can replace the switch with a dictionary of search paths and delegates or actions.
Because getting properties by reflection is quite hard to do, just use and modify this code:
public static void SortByAny(List<Person> listToSort, String sortBy)
{
if (sortBy == "Email")
listToSort.Sort((x, y) => x.Email.CompareTo(y.Email));
else if (sortBy == "Phone")
listToSort.Sort((x, y) => x.Phone.CompareTo(y.Phone));
else if (sortBy == "Age")
listToSort.Sort((x, y) => x.Age.CompareTo(y.Age));
else if (sortBy == "Id.NiNumber")
listToSort.Sort((x, y) => x.Id.NiNumber.CompareTo(y.Id.NiNumber));
else if (sortBy == "Id.Name.FirstName")
listToSort.Sort((x, y) => x.Id.Name.FirstName.CompareTo(y.Id.Name.FirstName));
else if (sortBy == "Id.Name.LastName")
listToSort.Sort((x, y) => x.Id.Name.LastName.CompareTo(y.Id.Name.LastName));
}

Comparing 2 lists of objects

I have several classes in my c# application.
I have a plant class with a constructor that takes a name and a weight.
Then I have a Fruit class that inherits plant and adds the number of seeds attribute.
Also I have a Veg class that inherits from plant and adds the savoryLevel attribute.
fruit and veg can be added to their lists by the user.
I have overloaded the == operator in fruit so that it compares the names of fruit and veg and if they have the same name it tells you. My issue is when I try to compare the whole lists to find duplication, I just cant get the code to work at all.
here is some of my code
plant class
public string name;
public string weight;
public Plant(string name, string weight)
{
this.name = name;
this.email = weight;
}
....
public static bool operator ==(Plant a, Plant b)
{
// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))
{
return false;
}
// Return true if the fields match:
return a.name == b.name;
}
then the new fruit constructor
string seeds;
public fruit(string name, string weight, string seeds)
: base(name, weight)
{
this.seeds
}
here is veg
string savoryLevel;
public fruit(string name, string weight, string savouryLevel)
: base(name, weight)
{
this.savoryLevel
}
here is the main where I compare 2 instances, this works fine
Fruit f = new Fruit("apple", "2", "5");
Veg v = new Veg("carrot", "3", "7");
if (f == v)
{
Console.WriteLine("They are the same");
}
else{
Console.WriteLine("They are different");
}
This is the tricky part, I need to iterate through my entire list of veg and fruit and see if any of the fruit have the same name as the veg.
using the lists directly wont work
List<Fruit> fr = new List<Fruit>();
List<Veg> ve = new List<Veg>();
if(fr == ve){
Console.....
}
else{
Console....
}
So how do I get the lists to compare and print out some result to say these are the same or these are not the same?
Any help is really appreciated, thanks.
please just ask if you would like more info.
I think you should use IEquatable<Plant> and cast the lists into List<Plant> with SequenceEquals()
Demo:
public class Plant : IEquatable<Plant>
{
public string Name { get; set; }
public string Weight { get; set; }
public override bool Equals(object obj)
{
var other=obj as Plant;
if(other!=null)
{
return Equals(other);
}
return false;
}
public bool Equals(Plant other)
{
Debug.WriteLine("Checking Equality Between {0} And {1}", Name, other.Name);
return Name.Equals(other.Name);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
public class Fruit : Plant
{
public string Seeds { get; set; }
}
public class Veg : Plant
{
public string SavoryLevel { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Fruit> fruits=new List<Fruit>() {
new Fruit() { Name="apple", Weight = "2", Seeds="5" },
new Fruit() { Name="banana", Weight="1", Seeds="30" }
};
List<Veg> veggies=new List<Veg>() {
new Veg() { Name = "carrot", Weight="3", SavoryLevel="7" },
new Veg() { Name = "potato", Weight="5", SavoryLevel="1" }
};
var equal=fruits.Cast<Plant>().SequenceEqual(veggies);
var unique_fruits=fruits.Distinct();
}
}
It produces the output
Checking Equality Between apple And carrot
and then the equality comparison ends (since it is false). The point being is that it calls the appropriate Equals() function.
If you wanna compare the items at the same index Zip method can be useful:
bool result = fr.Zip(ve, (f,v) => new { f, v }).All(x => x.f == x.v);
Zip methods create pairs of corresponding items, then put each pair into an anonymous type. And All method simply checks if all items in the pairs are equal.
if you want to to it per item, you could do it like this
foreach(var fruit in fr)
{
if(ve.Any(x => x.Name == fruit.Name))
{
Console.Write(fruit.Name + " is in both lists");
}
}
I'd use LINQ, and rather that (or in addition to) overloading the == operator, go for the "more native" object.Equals and object.GetHashCode.
public override int GetHashCode()
{
return this.name.GetHashCode();
}
public override bool Equals(object b)
{
Plant bPlant = b as Plant;
// If one is null, but not both, return false.
if (bPlant == null)
{
return false;
}
// Return true if the fields match:
return this.name == b.name;
}
Then you can use LINQ:
return fr.SequenceEquals(ve);
Note, of course, that, as the name implies, this only works when fr and ve are exactly equal. That is to say, the order must be the same between them: if both contain "Carrot, Broccoli," you'll be fine, but if one is that and the other is "Broccoli, Carrot," this will return false.
Unless I'm misunderstanding, and in fact you want the intersection, not to know that they're equal, in which case:
return fr.Intersect(ve);
You really don't need to overload the Equals method to find out if something is different. Overloading the Equals method should be used if you are looking for a different behaviour, not for different results.
Since this case compares two string members in two different classes, why not just use LINQ to compare the members themselves which are of the same datatype?
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
public static void Main()
{
List<Fruit> f = new List<Fruit>();
Fruit fTemp = new Fruit() { name = "Kiwi" };
f.Add(fTemp);
fTemp = new Fruit() { name = "Tomato" };
f.Add(fTemp);
List<Veg> v = new List<Veg>();
Veg vTemp = new Veg() { name = "Tomato" };
v.Add(vTemp);
List<Veg> vDuplicates = v.Where(vegToCompare=>f.Any(fruitToCompare=>fruitToCompare.name.Equals(vegToCompare.name))).ToList();
vDuplicates.ForEach(a=>Console.WriteLine(a.name));
Console.WriteLine("Number of Duplicates Found: " + vDuplicates.Count);
}
}
public class Fruit
{
public string name;
}
public class Veg
{
public string name;
}
First off thanks everyone for the input, You've all helped me see the problem more clearly.
But Overloading the == operator is something I had to do as part of the requirements.
I have however found a relatively simple way to compare the 2 lists that uses the overloaded == operator I added to the Plant Class
By nesting a forEach loop I check every list item of veg against every list item of fruit.
public void Compare(List<Fruit> frList, List<Veg> vList)
{
foreach (Fruit f in frList)
{
foreach (Veg v in vList)
{
if (f == v)
{
//some functionality
}else{
//some other funtionality
}
}
}
}
This still uses the overloaded == operator in plant and will only compare the name when I call the the method in the main. i.e. even if the plants have different weights they will be considered the same.
Thanks again for the input guys.

UnitTesting List<T> of custom objects with List<S> of custom objects for equality

I'm writing some UnitTests for a parser and I'm stuck at comparing two List<T> where T is a class of my own, that contains another List<S>.
My UnitTest compares two lists and fails. The code in the UnitTest looks like this:
CollectionAssert.AreEqual(list1, list2, "failed");
I've written a test scenario that should clarify my question:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ComparerTest
{
class Program
{
static void Main(string[] args)
{
List<SimplifiedClass> persons = new List<SimplifiedClass>()
{
new SimplifiedClass()
{
FooBar = "Foo1",
Persons = new List<Person>()
{
new Person(){ ValueA = "Hello", ValueB="Hello"},
new Person(){ ValueA = "Hello2", ValueB="Hello2"},
}
}
};
List<SimplifiedClass> otherPersons = new List<SimplifiedClass>()
{
new SimplifiedClass()
{
FooBar = "Foo1",
Persons = new List<Person>()
{
new Person(){ ValueA = "Hello2", ValueB="Hello2"},
new Person(){ ValueA = "Hello", ValueB="Hello"},
}
}
};
// The goal is to ignore the order of both lists and their sub-lists.. just check if both lists contain the exact items (in the same amount). Basically ignore the order
// This is how I try to compare in my UnitTest:
//CollectionAssert.AreEqual(persons, otherPersons, "failed");
}
}
public class SimplifiedClass
{
public String FooBar { get; set; }
public List<Person> Persons { get; set; }
public override bool Equals(object obj)
{
if (obj == null) { return false;}
PersonComparer personComparer = new PersonComparer();
SimplifiedClass obj2 = (SimplifiedClass)obj;
return this.FooBar == obj2.FooBar && Enumerable.SequenceEqual(this.Persons, obj2.Persons, personComparer); // I think here is my problem
}
public override int GetHashCode()
{
return this.FooBar.GetHashCode() * 117 + this.Persons.GetHashCode();
}
}
public class Person
{
public String ValueA { get; set; }
public String ValueB { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
Person obj2 = (Person)obj;
return this.ValueA == obj2.ValueA && this.ValueB == obj2.ValueB;
}
public override int GetHashCode()
{
if (!String.IsNullOrEmpty(this.ValueA))
{
//return this.ValueA.GetHashCode() ^ this.ValueB.GetHashCode();
return this.ValueA.GetHashCode() * 117 + this.ValueB.GetHashCode();
}
else
{
return this.ValueB.GetHashCode();
}
}
}
public class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x != null)
{
return x.Equals(y);
}
else
{
return y == null;
}
}
public int GetHashCode(Person obj)
{
return obj.GetHashCode();
}
}
}
The question is strongly related to C# Compare Lists with custom object but ignore order, but I can't find the difference, other than I wrap a list into another object and use the UnitTest one level above.
I've tried to use an IEqualityComparer:
public class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x != null)
{
return x.Equals(y);
}
else
{
return y == null;
}
}
public int GetHashCode(Person obj)
{
return obj.GetHashCode();
}
}
Afterwards I've tried to implement the ''IComparable'' interface thats allows the objects to be ordered. (Basically like this: https://stackoverflow.com/a/4188041/225808)
However, I don't think my object can be brought into a natural order. Therefore I consider this a hack, if I come up with random ways to sort my class.
public class Person : IComparable<Person>
public int CompareTo(Person other)
{
if (this.GetHashCode() > other.GetHashCode()) return -1;
if (this.GetHashCode() == other.GetHashCode()) return 0;
return 1;
}
I hope I've made no mistakes while simplifying my problem. I think the main problems are:
How can I allow my custom objects to be comparable and define the equality in SimplifiedClass, that relies on the comparision of subclasses (e.g. Person in a list, like List<Person>). I assume Enumerable.SequenceEqual should be replaced with something else, but I don't know with what.
Is CollectionAssert.AreEqual the correct method in my UnitTest?
Equals on a List<T> will only check reference equality between the lists themselves, it does not attempt to look at the items in the list. And as you said you don't want to use SequenceEqual because you don't care about the ordering. In that case you should use CollectionAssert.AreEquivalent, it acts just like Enumerable.SequenceEqual however it does not care about the order of the two collections.
For a more general method that can be used in code it will be a little more complicated, here is a re-implemented version of what Microsoft is doing in their assert method.
public static class Helpers
{
public static bool IsEquivalent(this ICollection source, ICollection target)
{
//These 4 checks are just "shortcuts" so we may be able to return early with a result
// without having to do all the work of comparing every member.
if (source == null != (target == null))
return false; //If one is null and one is not, return false immediately.
if (object.ReferenceEquals((object)source, (object)target) || source == null)
return true; //If both point to the same reference or both are null (We validated that both are true or both are false last if statement) return true;
if (source.Count != target.Count)
return false; //If the counts are different return false;
if (source.Count == 0)
return true; //If the count is 0 there is nothing to compare, return true. (We validated both counts are the same last if statement).
int nullCount1;
int nullCount2;
//Count up the duplicates we see of each element.
Dictionary<object, int> elementCounts1 = GetElementCounts(source, out nullCount1);
Dictionary<object, int> elementCounts2 = GetElementCounts(target, out nullCount2);
//It checks the total number of null items in the collection.
if (nullCount2 != nullCount1)
{
//The count of nulls was different, return false.
return false;
}
else
{
//Go through each key and check that the duplicate count is the same for
// both dictionaries.
foreach (object key in elementCounts1.Keys)
{
int sourceCount;
int targetCount;
elementCounts1.TryGetValue(key, out sourceCount);
elementCounts2.TryGetValue(key, out targetCount);
if (sourceCount != targetCount)
{
//Count of duplicates for a element where different, return false.
return false;
}
}
//All elements matched, return true.
return true;
}
}
//Builds the dictionary out of the collection, this may be re-writeable to a ".GroupBy(" but I did not take the time to do it.
private static Dictionary<object, int> GetElementCounts(ICollection collection, out int nullCount)
{
Dictionary<object, int> dictionary = new Dictionary<object, int>();
nullCount = 0;
foreach (object key in (IEnumerable)collection)
{
if (key == null)
{
++nullCount;
}
else
{
int num;
dictionary.TryGetValue(key, out num);
++num;
dictionary[key] = num;
}
}
return dictionary;
}
}
What it does is it makes a dictionary out of the two collections, counting the duplicates and storing it as the value. It then compares the two dictionaries to make sure that the duplicate count matches for both sides. This lets you know that {1, 2, 2, 3} and {1, 2, 3, 3} are not equal where Enumerable.Execpt would tell you that they where.

Optimizing Double foreach loop

I have a double foreach loop and want to speed it up by making it one loop instead of two.
The idea is that it takes one element from the dictionary and compares it against all elements in the dictionary
foreach (KeyValuePair<Int64, string> kvp in originCounts)
{
foreach (KeyValuePair<Int64, string> testkvp in originCounts)
{
//Run Comparison on testkvp ad kvp
}
}
I want to turn this into one loop, any suggestions?
You can use Enumerable.All to check if all elements are the same:
var firstID = originCounts.First().Value.UserID;
bool allEquals = originCounts.Skip(1).All(o => o.Value.UserID == firstID);
It seems that this is what you actually want.
I've just picked out a field of your class in the duplicate question that sounds as a reasonable identifier.
public class MyObject
{
public string FirstName{ get; set; }
public string LastName{ get; set; }
public int UserID { get; set; }
public string Address { get; set; }
}
Edit: According to your comment you want to determine if any of the objects' fields is different to the same field of another object.
var first = originCounts.First().Value;
bool allEquals = originCounts.Skip(1).All(o =>
o.Value.FirstName == first.FirstName
&& o.Value.LastName == first.LastName
&& o.Value.UserID == first.UserID
&& o.Value.Address == first.Address);
you could try this with linq syntax and compare performance
e.g.
foreach(KeyValuePair<long, string> kvp1 in originCounts.SelectMany(kvp1 => originCounts.Select(testkvp => kvp1), (kvp1, kvp) => kvp1)) {
}
or
foreach(KeyValuePair<long, string> kvp1 in from kvp1 in originCounts
from kvp in originCounts.Select(testkvp => kvp1)
select kvp1) {
}
You can create a comparable string key value class that implements the IComparable interface.
public class ComparableString : IComparable
{
public Int64 Key { get; set; }
public string Value { get; set; }
public int CompareTo(object obj)
{
if (obj == null) return 1;
string otherString = obj as ComparableString;
if (otherString != null)
{
// PLACE YOUR COMPARE LOGIC HERE
return this.Value.CompareTo(otherString.Value);
}
else
{
throw new ArgumentException("Object is not a Comparable String");
}
}
}
After doing that you can create a linked list and run the .sort method
var originCounts= new List<ComparableString>();
// Logic to fill your list
originCounts.Sort();
The average complexity is O(n(log n)) for the .Sort method and the worst case is O(n^2) see http://msdn.microsoft.com/en-us/library/b0zbh7b6.aspx for more information.
Once you call the .Sort() method you'll know that any value before the index of your item is less than or equal to the value of your item, and any index greater than the index of you're at is greater than or equal to the value of your item.
Perhaps I'm oversimplifying this by misunderstanding the question, but if you are just trying to find duplicate Values in a string, you can use the Values property of the Dictionary, and do a Distinct on them.
Using your example of a Dictionary:
Dictionary<Int64, string> originalCounts = new Dictionary<Int64, string>();
for (Int64 i = 0; i < 10; i++)
{
originalCounts.Add(i, i.ToString());
}
originalCounts[5] = originalCounts[3];
foreach (var kvp in originalCounts)
{
Console.WriteLine("{0} {1}", kvp.Key, kvp.Value);
}
Console.WriteLine();
foreach (var value in originalCounts.Values.Distinct())
{
Console.WriteLine("{0}", value);
}
If I understand correctly, to get the distinct objects (Which does not implement IComparable or override Equals and GetHashcode):
var noDups = originCounts
//Any Prop. you want to compare
.GroupBy(o => new { o.FirstName, o.LastName, o.UserID, o.Address })
.Select(g => g.First())
.ToList();
Why do you want to compare a dictionary entry to the other entries in the same dictionary? Are you checking for duplicates? If so, you can use originCounts.Distinct().
Edit: You would need to specify an IEqualityComparer for the Distinct method, otherwise it would only compare keys (which are always distinct in a Dictionary), not values.

C#: how to define an extension method as "with" in F#?

F# has a convenient feature "with", example:
type Product = { Name:string; Price:int };;
let p = { Name="Test"; Price=42; };;
let p2 = { p with Name="Test2" };;
F# created keyword "with" as the record types are by default immutable.
Now, is it possible to define a similar extension in C#?
seems it's a bit tricky, as in C# i'm not sure how to convert a string
Name="Test2"
to a delegate or expression?
public static T With<T, U>(this T obj, Expression<Func<T, U>> property, U value)
where T : ICloneable {
if (obj == null)
throw new ArgumentNullException("obj");
if (property == null)
throw new ArgumentNullException("property");
var memExpr = property.Body as MemberExpression;
if (memExpr == null || !(memExpr.Member is PropertyInfo))
throw new ArgumentException("Must refer to a property", "property");
var copy = (T)obj.Clone();
var propInfo = (PropertyInfo)memExpr.Member;
propInfo.SetValue(copy, value, null);
return copy;
}
public class Foo : ICloneable {
public int Id { get; set; }
public string Bar { get; set; }
object ICloneable.Clone() {
return new Foo { Id = this.Id, Bar = this.Bar };
}
}
public static void Test() {
var foo = new Foo { Id = 1, Bar = "blah" };
var newFoo = foo.With(x => x.Bar, "boo-ya");
Console.WriteLine(newFoo.Bar); //boo-ya
}
Or, using a copy constructor:
public class Foo {
public Foo(Foo other) {
this.Id = other.Id;
this.Bar = other.Bar;
}
public Foo() { }
public int Id { get; set; }
public string Bar { get; set; }
}
public static void Test() {
var foo = new Foo { Id = 1, Bar = "blah" };
var newFoo = new Foo(foo) { Bar = "boo-ya" };
Console.WriteLine(newFoo.Bar);
}
And a slight variation on George's excellent suggestion, that allows for multiple assignments:
public static T With<T>(this T obj, params Action<T>[] assignments)
where T : ICloneable {
if (obj == null)
throw new ArgumentNullException("obj");
if (assignments == null)
throw new ArgumentNullException("assignments");
var copy = (T)obj.Clone();
foreach (var a in assignments) {
a(copy);
}
return copy;
}
public static void Test() {
var foo = new Foo { Id = 1, Bar = "blah" };
var newFoo = foo.With(x => x.Id = 2, x => x.Bar = "boo-ya");
Console.WriteLine(newFoo.Bar);
}
I would probably use the second one since (1) any general purpose solution is going to be unnecessarily slow and convoluted; (2) it has the closest syntax to what you want (and the syntax does what you expect); (3) F# copy-and-update expressions are implemented similarly.
Maybe something like this:
void Main()
{
var NewProduct = ExistingProduct.With(P => P.Name = "Test2");
}
// Define other methods and classes here
public static class Extensions
{
public T With<T>(this T Instance, Action<T> Act) where T : ICloneable
{
var Result = Instance.Clone();
Act(Result);
return Result;
}
}
As an alternative to lambda function, you can use parameters with default values. The only minor issue is that you have to pick some default value that means do not change this parameter (for reference types), but null should be a safe choice:
class Product {
public string Name { get; private set; }
public int Price { get; private set; }
public Product(string name, int price) {
Name = name; Price = price;
}
// Creates a new product using the current values and changing
// the values of the specified arguments to a new value
public Product With(string name = null, int? price = null) {
return new Product(name ?? Name, price ?? Price);
}
}
// Then you can write:
var prod2 = prod1.With(name = "New product");
You have to define the method yourself, but that's always the case (unless you're going to use reflection, which less efficient). I think the syntax is reasonably nice too. If you want to make it as nice as in F#, then you'll have to use F# :-)
There is no native ability to do this in C# short of an extension method, but at what cost? a and b are reference types and any suggestion that b is based ("with") on a causes immediate confusion as to how many objects we are working with. Is there only one? Is b a copy of a ? Does b point to a ?
C# is not F#.
Please see a previous SO question of mine as answered by Eric Lippert:
"Amongst my rules of thumb for writing clear code is: put all side effects in statements; non-statement expressions should have no side effects."
More fluent C# / .NET

Categories

Resources