Update All References to Cached Object in Dictionary - c#

I have a simple cache of objects:
Dictionary<int, Person> personCache = ...
personCache.Add(1, new Person(){ID = 1, Name = "John"});
personCache.Add(2, new Person(){ID = 2, Name = "Mary"});
personCache[1].Manager=personCache[2];
(In reality, I have proper encapsulation of the dictionary)
So now John's manager is set to Mary. However, if I want to replace Mary with a new instance of person, if I do
personCache[2] = new Person(){ID = 2, Name = "Kate"});
References to Mary are not replaced with references to Kate - i.e. John's manager is not updated.
I can see why this is - the dictionary has a reference to Kate, but John still holds a reference to Mary.
Can anyone suggest a way such that
personCache[2] = new Person(){ID = 2, Name = "Kate"});
Would have the 'expected' result and replace all references to Mary with a reference to Kate?
I think I need to obtain the reference stored in personCache[2] and change that reference to a new person.
Thank you
Ryan

Why not just search for the Manager directly and updated it where it points to the old value
Person oldPerson = personCache[2];
Person newPerson = new Person() { ID = 2, Name = "Kate" };
personCache[2] = newPerson;
foreach (var pair in personCache) {
if (pair.Value.Manager == oldPerson) {
pair.Vaulue.Manager = newPerson;
}
}

"... Would have the 'expected' result "
I believe it already has what most people would consider to the the expected result.
One way to achieve what you appear to want is to make the Name property of your Person class writable, then do:
personCache[2].Name = "Kate";
Another approach would be to store only the Id of the manager in your Person object, rather than a reference to the Manager. I.e. instead of:
personCache[1].Manager = personCache[2];
You could have:
personCache[1].ManagerId = 2;
And you could, for example, have an extension method to get the Manager for a Person:
public static Person GetManager(this Person person)
{
if (person == null) throw new ArgumentNullException("person");
Person manager;
personCache.TryGetValue(person.ManagerId, out manager);
return manager; // returns null if not found in cache
}

Related

How to make a copy of an instance? [duplicate]

This question already has answers here:
Cloning queue in c#
(2 answers)
Closed 2 years ago.
In this following code, what I want to do is to add a new value to my queue and then calculate the average of my current queue. But I don't want to just perform the calculation directly on my current queue itself. Instead I want to first copy the content of the current queue to a new queue and perform the calculation on the new queue. But I know that since classes are reference variables, so if I perform change anything within the new queue, the original one will change along. So how do I make a copy of the original queue and make changes on it without changing the original one?
public class MovingAverage {
public int size;
public Queue<int> que = new Queue<int>();
public Queue<int> copy = new Queue<int>();
/** Initialize your data structure here. */
public MovingAverage(int size)
{
this.size = size;
}
public double Next(int val)
{
if (que.Count == size) que.Dequeue();
que.Enqueue(val);
copy = que;
double sum = 0;
while(copy.Count != 0){
sum = sum + copy.Dequeue();
}
return sum / que.Count;
}
}
To make a copy of a Queue you can pass an enumerable collection to the constructor of a new instance
var newQueue = new Queue<int>(existingQueue);
As your Queue contents are ints (not reference types) they will not be connected to the original elements but beware, if you did this with a Queue full of class of eg Person, that each Queue instance would reference the same Persons and changing eg the Name property of the head Person would mean that each Queue sees the change
var q1 = new Queue<Person>( new [] { new Person() { Name = "Joe" } } );
var q2 = new Queue<Person>(q1);
q1.Peek().Name = "Jane";
Console.WriteLine(q2.Peek().Name); //prints Jane
//in essence, in memory, you have this:
q1 --head--> Person{ Name = "Joe" }
q2 --head----^
Two queue instances, but both of them have a reference to the same Person;
If you alter something about the Person, such as their Name, both Queues see it
You are free to change something about the Queue itself, as they are different instances:
var q1 = new Queue<Person>( new[] { new Person() { Name = "Joe" }, new Person() { Name = "Jane" } } );
var q2 = new Queue<Person>(q1); //both queues have two people in
Console.WriteLine(q1.Peek().Name); //Joe
Console.WriteLine(q2.Peek().Name); //Also Joe
q1.Dequeue(); //bye bye Joe
Console.WriteLine(q1.Peek().Name); //Jane
Console.WriteLine(q2.Peek().Name); //Joe
But the different queues point to the same Persons so you cannot mutate the Person objects independently. To do that, you'll have to clone/make anew the Person objects too, perhaps like:
var q2 = new Queue<Person>(q1.Select(p => new Person { Name = p.Name }));

How can I create lots of objects with different property values?

I'm programming in C# and I want to instantiate lots of new objects to my application, all of the same type, but with different values for their properties. Example:
Student student1 = new Student();
student1.Name = "James";
student1.Age = 19;
student1.City = "Los Angeles";
Student student2 = new Student();
student2.Name = "Karen";
student2.Age = 20;
student2.City = "San Diego";
Student student3 = new Student();
student3.Name = "Bob";
student3.Age = 20;
student3.City = "Dallas";
This way of coding seems really wrong to me because what if I didn't need 3, but 500 students? What would be the best way to do it then?
I tried to use a for loop for this but that doesn't work because the property values differ.
What is the most efficient way to do this?
Thanks in advance.
In order to do anything with your objects at runtime you will probably want them in a list.
Without reading from a file or database, etc., the most concise way might be :
var Students = new List<Student>{
new Student { Name = "Bob", Age = 22, City = "Denver" },
new Student { Name = "Sally", Age = 33, City = "Boston" },
new Student { Name = "Alice", Age = 12, City = "Columbus" }
};
I don't know your end goal however, is this just mock data, like for a test?
Add constructor to Student like this
Student (string name, int age, string city)
{
Name = name;
Age = age;
City = city;
}
///
Student student1 = new Student("James", 19, "Los Angeles");
Well, if what you mean by more efficient way to do it is just to write less code, you could instanciate them assigning the property's values at once, just like:
Student student1 = new Student() { Name = "James", Age = 19, City = "Los Angeles" };
If you want not just to write less code, but to - let's say - read the data from another source (like a Json list, or a TXT file) you will have to write a loader for it.
Well, it depends what you are going to use it for. If it’s for testing, then you could use a custom built tool to create random Students:
public class RandomStudentCreator
{
private readonly Random rnd = new Random();
private readonly IList<string> cities, names;
private readonly int minAge, maxAge;
public RandomStudentCreator(
IList<string> names,
IList<string> cities,
int minimumInckusiveAge,
int maximumExclusiveAge)
{
//Argument validation here
this.cities = cities;
this.names = names;
minAge = minimumInckusiveAge;
maxAge = maximumExclusiveAge;
}
public Student Next()
{
var student = new Student();
student.Name = names[rnd.Next(names.Count);
student.City = cities[rnd.Next(cities.Count);
Student.Age = rnd.Next(minAge, maxAge);
}
}
If this is production code, then you should be creating students based on:
User input
Some data backend (DB, text file, etc.)
But in any case, you don’t want to create a variable for each student. You probably want a collection of students. Depending on what you want to do with them, the type of collection you need may vary, the framework gives you plenty of options:
Arrays: Student[]
Lists: List<Student>
Queues: Queue<Student>
Stacks: Stack<Student>
Sets: HashSet<Student>
Etc.
And last but not least, you probably want to implement a constructor in Student that takes a name, city and age to make instantiation a little bit more compact than what you currently have:
public class Student
{
public Student(string name,
int age,
string city)
{
Name = name;
Age = age;
City = city;
}
//...
}
var john = new Student(“John”, 19, “LA”);
Programming is not about typing data. Need a lot of data? - Load them from files, databases, servers, through GUI, etc.
You can make a handy constructor, you can make factories and builders, but they are not for creating hundreds of objects in a row. Even if it is historical data, one day you will want to change them, fix something in them. Believe me, it's much easier to separate them from the code and store somewhere else, than to edit hundreds of lines of code later.
If you want 500 students I suggest extracting data to a file, database etc. student1..student499 implementation looks very ugly: let's organize them into array: Student[] students. As an example, let's use the simplest csv file Students.csv solution in the format
name,age,city
E.g.
name,age,city
James,19,Los Angeles
Karen,20,San Diego
Bob,20,Dallas
Having the file completed you can easily read it:
using System.IO;
using System.Linq;
...
Student[] students = File
.ReadLines("Students.csv")
.Where(line => !string.IsNullOrWhiteSpace(line)) // Skip empty lines
.Skip(1) // Skip header
.Select(line => line.Split(','))
.Select(items => new Student() {
Name = items[0],
Age = int.Parse(items[1]),
City = items[2], })
.ToArray();

assign object from the list using its index

If I have list of Author objects like
List<Author> authors = new List<Author>{
new Author { Id = 1, Name = "John Freeman"};
new Author { Id = 1, Name = "Adam Kurtz"};
};
this example is actually wrapped in static method which returns list of authors.
Inside my other object there is property Authors of type List<Author>.
Now I want to assign second author from the list to Authors property.
I thought that I can use Authors = GetAuthors()[1].ToList();
but I cannot access ToList() on index specified author.
to clarify
private static List<Author> GetAuthors() return list of authors (example above).
var someObject = new SomeObject()
{
Authors = // select only Adam Kurtz author using index
// and assign to Authors property of type List<Author>
};
If I understand you correctly, you want a List<Author> with a single author in it. Using ToList() on a single Author object is then not valid syntax.
Try this: Authors = new List<Author>() { GetAuthors()[1] };
You can't assign a single author to list<Author>, so you will have to create a list (of single author) to assign it..
Authors = new List<Author>() {GetAuthor()[1]};
I don't know, why you want to take based on index, ideally you should write a query based on ID of author to get value, so that it won't create any issue in future..
Like: Authors = new List<Author>() {GetAuthor().FirstOrDefault(x=>x.ID==2)};
A sollution with LINQ would be
GetAuthors().Skip(1).Take(1)
Edit: Ignore all that. You're working with lists.
What you actually need is to use GetRange:
GetAuthors().GetRange(1,1);

How to replace a list item with direct instance assignment

I have the following code where I'm trying 3 approaches (Cases) to update the first item in a C# list(Note: Dump() is a helper output method in the LINQPad IDE). I would appreciate an explanation as to why Case 2 does not succeed in updating the list while Case 3 does. Both first and list[0] are references to the first item in the list and should behave equivalently when assigned a direct reference. Apparently not...
void Main()
{
Person first = null;
List<Person> list = CreateList(out first);
//Case 1
//This updates the list
first.fname = "Third";
list.Dump(); //outputs third, second
//Case 2
//This does not update the list
list = CreateList(out first);
first= new Person() { fname="Third"};
list.Dump(); //outputs first, second
//Case 3
//This updates the list
list = CreateList(out first);
list[0] = new Person() { fname="Third"};
list.Dump(); //outputs third, second
}
List<Person> CreateList(out Person first)
{
var list = new List<Person>
{
new Person() { fname="First", lname = ""},
new Person() { fname="Second", lname = ""}
};
first = list.Find( x => x.fname == "First");
return list;
}
// Define other methods and classes here
class Person
{
public string fname;
public string lname;
}
The second case doesn't work because you change the reference for the first to the new object using this code:
first= new Person() { fname="Third"};
After this code run, the first is not refer to list object again.
Try to use this for the second case:
list = CreateList(out first);
if(first != null)
first.fname="Third";
list.Dump();
This will set the property of first and the first is still refer to the list item.
when you pass a new object to a reference object
first= new Person() { fname="Third"};
you generate a new object with a new hashcode upon which the object is identified in collection.the list does not find the previous hascode and thus list is not updated.//case 2
but in case 3 you are replacing the instance of object and thereby list updates the new hash
in case 1 you modify only property of an object and the hash remains intact
may be this be explanation to your problem

CRM 2011 Create Product Plugin

I am trying to create a new product with a plugin, but I get this exception:
System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
This is the code to create the product
EntityReference ugRef = new EntityReference(ug.LogicalName, ug.UoMScheduleId.Value);
EntityReference uRef = new EntityReference(u.LogicalName, u.UoMId.Value);
Product product = new Product()
{
Name = pName,
ProductNumber = pNumber,
QuantityDecimal = 2,
DefaultUoMScheduleId = ugRef,
DefaultUoMId = uRef
};
service.Create(product);
All variables have been tested, they all have values. The unit is correct for the unit group - if I change either I get an exception saying as much.
The problem is definitely with this piece of the code as there is a lovely lead with the expected 1st and last name when the code is altered to this:
EntityReference ugRef = new EntityReference(ug.LogicalName, ug.UoMScheduleId.Value);
EntityReference uRef = new EntityReference(u.LogicalName, u.UoMId.Value);
Lead l = new Lead();
l.FirstName = uRef.Id.ToString();
l.LastName = uRef.LogicalName;
service.Create(l);
/*
Product product = new Product()
{
Name = (String)staged.Attributes["wishlist_name"],
ProductNumber = (String)staged.Attributes["wishlist_barcode"],
QuantityDecimal = 2,
DefaultUoMScheduleId = ugRef,
DefaultUoMId = uRef
};
service.Create(product);
*/
pName and pNumber are strings.
u and ug are a Unit and a Unit Group.
I changed the code to:
query = new QueryByAttribute("uom");
query.ColumnSet = new ColumnSet("name", "uomscheduleid");
query.Attributes.AddRange("name");
query.Values.AddRange("1");
UoM unit = (UoM)service.RetrieveMultiple(query).Entities[0];
Product newProduct = new Product
{
ProductNumber = "1t2y3u",
Name = "Example Banana Product",
QuantityDecimal = 1,
DefaultUoMScheduleId = unit.UoMScheduleId,
DefaultUoMId = unit.ToEntityReference()
};
service.Create(newProduct);
The same error is thrown.
I am about to strip my moer with this.
Couple things to look at.
It looks like your tried to simplify your code in the first example, but may have removed the source of your bug, but luckily, you added it to your last example :) I'm guessing staged does not contain "wishlist_name", and therefor is giving you the error you see. You should always use the typed GetAttributeValue method defined in the Entity class: staged.GetAttributeValue<String>("wishlist_name"). It will perform a null check and return the default for the type.
Check all of your other plugins, to see if another plugin is fired upon the creation of the Product, that is possibly doing some extra logic if the DefaultUoMScheduleId or DefaultUoMId is populated. Your create in this plugin could be getting an error from another "nested" plugin.
Instead of creating temporary Entity Reference variables, use the ToEntityReference() method defined in the entity class, it makes the code look a little cleaner IMHO.
Product product = new Product()
{
Name = (String)staged.Attributes["wishlist_name"],
ProductNumber = (String)staged.Attributes["wishlist_barcode"],
QuantityDecimal = 2,
DefaultUoMScheduleId = ug.ToEntityReference(),
DefaultUoMId = u.ToEntityReference()
};

Categories