I am a beginner and currently working on a project for practice. My goal is to create an Adress Book application. What I want to do is I am asking the user to pass in a name. And I store that name in a variable. Is it possible for me to use that string variable to name the object? I have looked for solutions and they all suggest to have a Constructor that takes a name and assigns it but I already have that and it is not what I want. I am storing all these Person variables in a Person List(That's why I am using the loop) and later, I want to build a system to browse through Adress Book and search for stuff. So my overall question is- Can I use a string variable to name the object. Is there any way to do that?
while (true)
{
Console.WriteLine("Please enter names to the adress book or type \"quit\" to finish");
var input = Console.ReadLine();
var name = input;
if (string.IsNullOrEmpty(input))
{
throw new IsNullException("Name can not be null or empty");
}
if (input.ToLower() == "quit")
{
break;
}
Person person = new Person(input);
AdressBook.Add(person);
}
No. A variable name is a convenience to the programmer, but it conveys no information to the program. Also note that a variable is just a reference to an object; it is not "the name of the object" (there might actually be many variables that reference the same object).
However, there are situations in which it is convenient to be able to tie an object to another piece of information in order to be able to look the object up by that information later. The general computer science term for this is a hash table, and in C#, it's called a Dictionary. You use it like this:
var peopleByName = new Dictionary<string, Person>();
string name = Console.ReadLine();
Person person = new Person(name);
peopleByName[name] = person;
Person theSamePerson = peopleByName[name];
theSamePerson, which was obtained by asking peopleByName for the object that is tied to the value of the name variable, will now refer to the same object that was added to the dictionary under that name.
I suspect that you are looking for Dictionary<TK,TV>. You can use a dictionary to map from a string to any object you like:
// create the dictionary to hold your records.
var records = new Dictionary<string,AddressRecord>();
var item = new AddressRecord("Mary", "Smith", "1234 N Park Ln", "Springfield", "OH");
var key = item.FirstName + " " + item.LastName;
records.Add(key, item);
// try to find someone by name
AddressRecord record;
var key = "Mary Smith";
if(records.TryGetValue(key, out record)) {
// use the record
Console.WriteLine("The address for "+ key + " is " + record.Address);
} else {
Console.WriteLine("No record found for " + key);
}
// or iterate over all the records:
foreach(var record in records.Values) {
Console.WriteLine(record.FirstName + " " record.LastName);
}
Of course the dictionary requires that all of it's keys are unique, so you might have problems if you know more than one person named Jon Smith.
I have looked for solutions and they all suggest to have a Constructor that takes a name and assigns it but I already have that and it is not what I want.
Can you explain why this isn't what you want? It is common and natural in .NET to give names to objects in this way. You could just write your Person class like this:
class Person
{
private string _name;
public Person(string input)
{
_name = input;
}
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
}
...and then access the object's name by calling its Person.Name property:
var somePerson = new Person("Bob");
Console.WriteLine(somePerson.Name);
In fact, this basically is how individual controls in Windows Forms are assigned names.
Furthermore, assuming your AdressBook variable is declared as a List<Person>, then you can access an individual person's name like this:
// Get the name of the third person added to the AdressBook list.
Console.WriteLine(AdressBook[2].Name);
If for some reason you're not making clear you want to store each Person object's name separately from the object itself, then the Dictionary<string, Person> approach mentioned in Aasmund's answer is perfectly fine. Another option to explore if you really want to stick with the List container type could maybe be a List<Tuple<string, Person>> variable using the .NET Tuple(T1, T2) type.
Without more detail on your requirements, there are dozens of ways you could do this.
Related
I'm creating a practice application to practice using text files as a database.
Take a pipe delineated text file with 14 People Data Entries
Split the text file on the pipes and add each entry to a add each entry (about 150) to a list of the objects.
I've casted list of objects to a string.
Now I want a button click to display that list in a textbox.
Here is the code.
namespace StaffRosterLewis.ViewModel
{
class LoadData
{
public static void LoadRosterData()
{
string mypath = #"J:\zUtilities - Program Files\";
mypath += "StaffRoster - RosterOld.txt";
List<Model.Person> people = new List<Model.Person>();
List<string> lines = File.ReadAllLines(mypath, Encoding.ASCII).ToList();
foreach (var line in lines)
{
string[] entries = line.Split('|');
Model.Person newPerson = new Model.Person
{
LastName = entries[1],
FirstName = entries[2],
Extension = entries[3],
Department = entries[4],
Team = entries[5],
Group = entries[6],
Title = entries[7],
Shift = entries[8],
EmergencyResponder = entries[9],
AEDCPRCert = entries[10],
Languages = entries[11],
Notary = entries[12],
Note = entries[13],
DutyLocation = entries[14]
};
//newPerson.Unknown15 = entries[15];
people.Add(newPerson);
}
people.ToString();
}
}
}
I think the problem is here where the button click happens. I am simply trying to show the content of people above, but I lose the variable "people" no matter how I try to reference it. The above code is public and I made it static so the people variable should be accessible anywhere within the project. (I thought)
private void Button_Show_Click(object sender, RoutedEventArgs e)
{
Button_Show.Content = $"{LoadData.LoadRosterData.people.ToString()}";
}
There are many problems with your code.
You are trying to access a method like a static property here.
Second, you have return type a void, which should be of type string.
Third you should override the ToString method to return the list items as sting in the required format.
You should use Path.Combine to get the path.
If you are planning to make the people as static variable then you have to accept that it’s not thread safe and make sure you reset it whenever it’s necessary else you may face unexpected items in the list.
And your code will throw exception if you have less than 15 |in a line
You don't need to make LoadRosterData static. However, as mentioned by other commenters the method must return a result (in context of your current usage).
Because reading from a file can be resource consuming, you could increase performance when you store the result in a e.g. public property Peoples.
This allows to access the collection without forcing to read the probably unchanged file. Since this would mean to introduce an instance variable, it's recommended to make LoadRosterData an instance member too.
To create a string from the collection you can use the StringBuilder. The below example uses the StringWriter, which allows asynchronous string creation. StringWriter also uses a StringBuilder internally.
You can further improve the code by overriding ToString of the Person type.
To improve the file reading performance you should use the asynchronous API of the StreamReader instead of the synchronous File class. Using StreamReader.ReadLineAsync will also save you an extra loop.
To make file handling and data model creation in particular more convenient, you should consider to make use of serialization.
The recommended text format would be JSON. See Microsoft Docs: How to serialize and deserialize (marshal and unmarshal) JSON in .NET to learn how to do it. Deserializing the JSON file (preferably asynchronously) will automatically produce a collection of Person items without any hassle (this will eliminate the ugly and fragile index based access (to initialize instance properties) as well as making any delimiters like your pipe '|' redundant).
You are accessing the array starting from index '1'. But in computer science, index always starts from '0'. Not sure if you start from index '1' on purpose.
The following code fixes some issues and implements some performance improvements. It also shows how to convert the collection to a string, where each Person item is displayed in it's own line:
PersonDataReader.cs
class PersonDataReader
{
// Consider to use an ImmutableList to prevent modification.
// In the current context, this property could (and probably should) defined private.
public List<Person> Persons { get; }
// If Person is private, 'HasData' has to be defined private too
public bool HasData => this.Persons.Any();
// Constructor
public PersonDataReader() => this.Persons = new List<Person>();
public async Task<string> CreateRosterSummaryAsync()
{
// Use StringWriter to enable asynchronous string creation.
// StringWriter also uses a StringBuilder internally to improve performance.
using (var textWriter = new StringWriter())
{
if (!this.HasData)
{
await LoadRosterDataAsync();
}
// Alternatively use LINQ,
// for example Enuemrable.Select together with Enumerable.Aggregate
// to concatenate the Person.ToString values
foreach (Person person in this.Persons)
{
string personString = person.ToString();
// Write a Person per line
await textWriter.WriteLineAsync(personString);
}
return textWriter.ToString();
}
}
private async Task LoadRosterDataAsync()
{
this.Persons.Clear();
// Use Path.Combine to ensure a valid formatted path (improve robustness)
string sourcePath = Path.Combine(#"J:\zUtilities - Program Files", "StaffRoster - RosterOld.txt");
// Test if the file actually exists to avoid the expensive exception.
// If the file not found exception is desired, remove the File.Exists condition.
if (File.Exists(sourcePath))
{
return;
}
using (var fileReader = new StreamReaeder(sourcePath, Encoding.ASCII))
{
while (!fileReader.EndOfFile)
{
var line = await reader.ReadLineAsync();
string[] personValues = line.Split('|', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
// If you would use serialization, constructing the type explicitly
// can be avoided. A recommended text format to allow easy de-/serialization is the JSON format.
var newPerson = new Model.Person
{
// Check if starting from index '1' is really correct
LastName = personValues[1],
FirstName = personValues[2],
Extension = personValues[3],
Department = personValues[4],
Team = personValues[5],
Group = personValues[6],
Title = personValues[7],
Shift = personValues[8],
EmergencyResponder = personValues[9],
AEDCPRCert = personValues[10],
Languages = personValues[11],
Notary = personValues[12],
Note = personValues[13],
DutyLocation = personValues[14]
};
this.Persons.Add(newPerson);
}
}
}
}
Person.cs
class Person
{
/* Properties of Person */
// Example text representation of a 'People' object
// using string interpolation
public override string ToString()
=> $"Lastname: {this.LastName}; Firstname: {this.FirstName}; Duty location: {this.DutyLocation}";
}
MainWindow.xaml.cs
partial class MainWindow : Window
{
private PersonDataReader PersonDataReader { get; }
public MainWindow()
{
InitializeComponent();
this.PersonDataReader = new PersonDataReader();
}
private async void Button_Show_Click(object sender, RoutedEventArgs e)
{
string personPerLineText = await this.PersonDataReader.CreateRosterSummaryAsync();
// Why did you chose a Button here? It should be
// this.DisplayTextBox.Text = personPerLineText;
this.Button_Show.Content = personPerLineText;
// Consider to display the complete List<Person> in a ListBox.
}
}
Remarks
A ListBox is probably the better choice to display the list of Person. It's more intuitive to implement and would eliminate the need to worry about string representations and creation.
Your method LoadRosterData has a return value void, means nothing. If you would like to return a string value and use it you may do it like this:
//returns a string
public static string MyStringMethod()
{
string str = "My string";
//Specify the return value with the return keyword
return str;
}
Or if you want to return a list:
public static List<string> MyListMethod()
{
List<string> list = new List<string>();
list.Add("one");
list.Add("two");
list.Add("three");
return list;
}
You can't do ToString() with list, but you can refer to the index of a person.
And the useage:
button.Content = $"{MyStringMethod()}"
Or if you want to get person by name you can use IndexOf() method:
List<string> list = new List<string>();
list.Add("one");
list.Add("two");
list.Add("three");
string number = "one";
button.Content = $"list[list.IndexOf(number)]";
Given a record
public record Address(string Name);
How can I access the original value of a property in a with expression? What I would like to do is something like:
var address = new Address("foo");
var extendedAddress = address with { Name = $"{Name} - bar" }; // does not compile
so that extendedAddress == "foo - bar".
Is there a way of referencing the original record that is being overridden so that the values can be "extended"?
TO BE MORE SPECIFIC
The example I gave is misleading since one could simply do:
var extendedAddress = address with { Name = $"{address.Name} - bar" };
But what I'm looking for is to achieve this within a single expression. Something like:
customers.Select(x => x.GetAddress() with Name = $"{Name} - bar")
There's nothing automatic here, for good reason: Name already has resolution rules (an instance member this.Name, a static Name, a type called Name, etc). Just use address.Name from a local, perhaps via a let expression in LINQ
I have 5 different classes that all inherit from BaseEntity. I would like to create a new model class that will store information needed about one of these 5 classes as well as other identifiers.
When I retrieve the data for this new model from the database, all I get is a string with the class type along with an integer that represents which entry I can reference from the database.
For example, if I retrieve Id = 2, Type = "BaseBall". That means I will have need to use my BaseBallService to fetch the entry where Id == 2. If it happens to be Id = 2, Type = "BasketBall", then I will use BasketBallService.
Currently the only solution I can think of is it to have a bunch of if statements that evaluate the 'type' string. Depending on if the type matches a valid type (BaseBall, FootBall, BasketBall, etc.) then that object is returned.
Is there a way to easily do this without the need to define all 5 types in the model definition and stringing if or statements to identify this?
I hope I have identified the problem clearly enough. Let me know if any additional information is needed. I haven't written any code for this yet. I am merely trying to analyze the problem and form a solution.
I would just add a global enum at the project or solution level to store types. That way if you wish to add to it later you may without breaking any existing code as it is detached. But this may keep it well typed and thus demand a type that is listed from the end user or application. I did a simple console app to show this. You may apply the enum to any class not just a generic though. I also implement a return method to narrow down the return lists to show how I can get lists of my lists easier.
public enum types
{
Type1,
Type2,
Type3
}
public class GenericListing
{
public string Description { get; set; }
public types Type { get; set; }
}
class Program
{
public static List<GenericListing> GetTypeListing(List<GenericListing> aListings, types aTypes)
{
return aListings.Where(x => x.Type == aTypes).ToList();
}
static void Main(string[] args)
{
var stuff = new List<GenericListing>
{
new GenericListing {Description = "I am number 1", Type = types.Type1},
new GenericListing {Description = "I am number 2", Type = types.Type2},
new GenericListing {Description = "I am number 3", Type = types.Type3},
new GenericListing {Description = "I am number 1 again", Type = types.Type1},
};
string s = "";
GetTypeListing(stuff, types.Type1) // Get a specific type but require a well typed input.
.ForEach(n => s += n.Description + "\tType: " + n.Type + Environment.NewLine);
Console.WriteLine(s);
Console.ReadLine();
}
}
You may try using Dictionary, e.g.
Dictionary<String, BaseEntry> types = new Dictionary<String, BaseEntry>() {
{"BaseBall", new BaseBallService()},
{"BasketBall", new BasketBallService()},
...
}
...
var value = types["BaseBall"].GetId(2);
This question already has answers here:
How to update the value stored in Dictionary in C#?
(10 answers)
Closed 9 years ago.
I have a dictionary of members where the key is a unique long ID and the value is an object which contains data on that members name surname and other forms of member details. Is there any way in C# that this can be done?
e.g
dictionary key holds memberID 0 member
id 0 name is bob lives in Italy
bob moves to England
is there a way to update the dictionary in C# so that his entry now says he lives in England?
Assuming that Member (or whatever) is a class, it's simple:
members[0].Country = "England";
You're just updating the object which the dictionary has a reference to. Just to step through it, it's equivalent to:
Member member = members[0];
member.Country = "England";
There's only one object representing Bob, and it doesn't matter how you retrieve it.
In fact, if you already have access to the instance of Member via a different variable, you don't need to use the dictionary at all:
// Assume this will fetch a reference to the same object as is referred
// to by members[0]...
Member bob = GetBob();
bob.Country = "England";
Console.WriteLine(members[0].Country); // Prints England
If Member is actually a struct... well, then I'd suggest rethinking your design, and making it a class instead :)
For classes (at least, those that are mutable) this should be as simple as:
long theId = ...
yourDictionary[theId].Country = "England"; // fetch and mutate
For structs (which should be immutable; or also for immutable classes), you will need to fetch, re-create, and overwrite:
long theId = ...
var oldItem = yourDictionary[theId]; // fetch
var newItem = new SomeType(oldItem.Id, oldItem.Name, "England"); // re-create
yourDictionary[theId] = newItem; // overwrite
(obviously the re-create line needs tweaking to your particular objects)
In the evil evil world of mutable structs (see comments), you can mutate once it is in a variable:
long theId = ...
var item = yourDictionary[theId]; // fetch
item.Country = "England"; // mutate
yourDictionary[theId] = item; // overwrite
dictionary[memberID].Location = "Italy";
Well, I can't outcode Marc or Jon but here's my entry: (I used City instead of Country but the concept is the same.)
using System;
using System.Collections.Generic;
public class MyClass
{
public static void Main()
{
var dict = new Dictionary<int, Member>();
dict.Add(123, new Member("Jonh"));
dict.Add(908, new Member("Andy"));
dict.Add(456, new Member("Sarah"));
dict[456].City = "London";
Console.WriteLine(dict[456].MemberName + " " + dict[456].City);
Console.ReadKey();
}
}
public class Member
{
public Member(string name) {MemberName = name; City="Austin";}
public string MemberName { get; set; }
public string City { get; set; }
// etc...
}
I"m populating a DropDownList from a strongly typed list, and that is working fine. The issue is I want to concatenate two of the fields within the list first and then put them in the dropdown. i.e., FirstName + LastName. I've tried a few things that didn't pan out, so can someone give this novice a lil help.
This is an example of what I'm doing.
private List<Customer> _CustomerList = new List<Customer>();
ddlCustomer.DataSource = _CustomerList;
ddlCustomer.DataTextField = "FirstName";
ddlCustomer.DataValueField = "CustomerKey";
ddlCustomer.DataBind();
this works but I need first and last together and I can't manipulate that data in the Customer object.
Try using an enumeration of an anonymous object created on the fly.
var _CustomerList = customers.Select( c => new {
Name = c.FirstName + " " + c.LastName,
Key = c.CustomerKey
});
ddlCustomer.DataSource = _CustomerList;
ddlCustomer.DataTextField = "Name";
ddlCustomer.DataValueField = "Key";
ddlCustomer.DataBind();
You may have to add a ToList() after the Select, but I think you can bind to an IEnumerable<T>.
P.S. This example requires the .Net 3.5 Framework
You can create a property in Customer class which concatenate FirstName and LastName and you can use that property in your ddlCustomer.DataTextField
I have yet to find a good solution to this problem. I've found the following approach to work the best:
Create a wrapper class around Customer (either inherit from Customer if possible, or create an entirely new class that holds a Customer object and exposes further properties.
Create a property in your NEW class that concatenates the two fields you wish to databind to.
Bind the dropdown to a List of your custom object.
To separate the UI representation from the underlying data object, you can create a wrapper object around Customer, such as CustomerForUI, with a single property called FullName - and then put a list of CustomerForUI objects into the UI.
Something like:
public class CustomerForUI
{
private string _FullName;
public CustomerForUI(Customer c)
{
_FullName = c.FirstName + " " + c.LastName;
}
public string FullName
{
get {return _FullName;}
}
public string CustomerKey
{ ... }
}
and construct a list of CustomerForUI objects called _UIList, and:
ddlCustomer.DataSource = __UIList;
ddlCustomer.DataTextField = "FullName";
ddlCustomer.DataValueField = "CustomerKey";
ddlCustomer.DataBind();
Is there a reason why you can't manually build up the list items in the drop-down list? Since you're already doing this in code, I don't see why not.
Just enumerate your business object, and use:
ddlCustomer.Items.Add(new ListItem("text" , "value"));
where text is the first and last name concatenated together.
I agree with ScottE...that would seem to be the simplest way, and would have one less object to keep track of.