I cannot modify a variable from a closure, why? - c#

I wanted to see if I can write some c# code in a javascript-fashion way but I got an unexpected behaviour. I know c# is not broken but I don´t understand what is wrong and why it doesn´t do what I think it should.
Code:
class Program
{
static void Main(string[] args)
{
var person = CreatePerson(new{
FirstName = "Lucas",
LastName = "Ontivero",
Age = 34
});
Console.WriteLine("Name....... : {0}", person.FullName);
Console.WriteLine("IsJuvenile. : {0}", person.IsJuvenile);
person.SetFirstName("Pablo");
Console.WriteLine("Name....... : {0}", person.FullName);
Console.ReadKey();
}
static private dynamic CreatePerson(dynamic cfg)
{
string firstName = cfg.FirstName;
string lastName = cfg.LastName;
var age = cfg.Age;
return new{
FullName = string.Format("{0} {1}", firstName, lastName),
FirstName = firstName,
LastName = lastName,
SetFirstName = new Action<string>(fn => firstName= fn),
SetLastName = new Action<string>(ln => lastName = ln),
IsJuvenile = age < 18
};
}
}
Output:
Name....... : Lucas Ontivero
IsJuvenile. : False
Name....... : Lucas Ontivero
But I expected:
Name....... : Lucas Ontivero
IsJuvenile. : False
Name....... : Pablo Ontivero
Why firstName doesn´t change?

It does... but FullName does not. It is computed inside CreatePerson and stored as a string, not a property evaluating to a string later.
You could use lambdas to cause delayed evaluation, as you did with SetFirstName.

It gives you fist assignment of FullName.
You need to re-assign FullName
FullName = string.Format("{0} {1}", firstName, lastName)
On SetFirstName Function

Related

SQLite on C#. Select query does not work with string of letters

I'm learning C# since the past week and databases since a couple of days ago, so if you see something dodgy and you ask yourself "Why did he do that like that?", the answer is probably going to be "Because that's all I know for the moment".
In the title I said "of letters" because if I use a string of numbers, it works.
I have a tiny database with three columns. Id (int), FirstName (text) and LastName (text).
Id is unique, primary key and autoincrements. FirstName is unique. The three are not null.
In that database I have two records:
Id FirstName LastName
3- 6666 2222
4- Test O'Test
This is my method:
public static bool isOnDb(string nombre, string apellido)
{
bool flag = false;
{
try
{
using (IDbConnection cnn = new SQLiteConnection(LoadConnectionString()))
{
string tempName = "Test"; // This is temporarily replacing the argument 'nombre'
int tempNum= 3; // More testing. See below
//cnn.Query<Person>($"select * from Person where FirstName = { tempName }", new DynamicParameters());
// This four lines below are just for testing. They are going to be deleted
var output = cnn.Query<Person>($"select * from Person where FirstName = { tempName }", new DynamicParameters());
var person = output.First();
Console.WriteLine("Funciona");
Console.WriteLine($"{ person.Id } - { person.FullName }");
flag = true;
return flag;
}
}
catch (Exception)
{
Console.WriteLine("Derecho a excepcion");
return flag;
}
}
}
Basically, if tempName = "Test", it falls into an exception. But if tempName = "6666" it returns the row.
I also tried selecting by id. That's why tempNum is there.
int tempNum= 4;
var output = cnn.Query<Person>($"select * from Person where Id = { tempNum }", new DynamicParameters());
Well in SQLite the strings are signified by '' so when you pass FirstName = { tempName } and its FirstName = Test it looks for the column called Test in Table person rather than equating it to the 'Test' string
So you can do:
var output = cnn.Query<Person>($"select * from Person where FirstName = '{ tempName }'");
Or better yet:
var people = cnn.Query<Person>("SELECT * FROM PERSON WHERE FirstName = #FirstName", new { FirstName = tempName });

Split full name into first, last and titles

example of the column
I have a column called FULLNAME which consists of a variation of FIRSTNAME and SURNAME separated by a space, and TITLE, FIRSTNAME and SURNAME, all separated by a space. So for example, I could have JOHN SMITH or DR JOHN SMITH.
I am using C# in Visual Studios.
I loop through each of these as per:
foreach (DataRow dr in spreadsheetdata.Tables["EPABase"].Rows)
And my array for the title is as:
title[0] = "Mr";
title[1] = "Mrs";
title[2] = "Miss";
title[3] = "Ms";
title[4] = "Dr";
title[5] = "Rev";
title[6] = "Hon";
title[7] = "Mx";
title[8] = "Other";
It doesn't matter which way around I work, but it's probably easier to get the SURNAME first because it'll always be the the last set of characters in a string, up to the first space value from the right. If I an get this into it's own string of SURNAME, then remove it from the original FULLNAME string, I can then use my array to see if the first set of characters from the left up to the first space appears in it, and if so use as the TITLE string, but if not, use the remaining string after the deletion of the SURNAME to be the FIRSTNAME string.
Just a bit stuck as to how to achieve the first step of this, getting the SURNAME out. I have tried LASTINDEXOF, but this is an integer value, and I need string.
If you are sure that First name or Last Name don't have space in it, you try something like this:
string[] fullNames = { "John Smith", "Dr John Smith" };
string[] titles = { "Mr", "Mrs", "Dr" };
foreach (var item in fullNames)
{
var details = item.Split(' ');
if (titles.Contains(details[0]))
{
Console.WriteLine($"Title: { details[0]} ");
Console.WriteLine($"First Name: { details[1]} ");
Console.WriteLine($"Last Name: { details[2]} ");
}
else
{
Console.WriteLine($"First Name: { details[0]} ");
Console.WriteLine($"Last Name: { details[1]} ");
}
}
To get the surname you can do something as follows:
foreach (DataRow dr in spreadsheetdata.Tables["EPABase"].Rows)
{
var value = dr["FULLNAME"].ToString();
var elementsOfName = value.Split(" ");
var lastName = elementsOfName[elementsOfName.Length - 1];
Console.WriteLine(lastName);
}

F#: Why those two collections are not equal?

Was writing some units using XUnit until that at some points I bumped into something surprising:
let id = Guid.Empty
let contact = {
Name = {
FirstName = "Marcel"
MiddleInitial = None
LastName = "Patulacci"
}
DateOfBith = new DateTime(1850, 12, 25)
Address = {
Address1 = "41 av 8 Mai 1945"
Address2 = None
City = "Sarcelles"
State = None
Zip = "95200"
}
PhoneNumber = {
DialOutCode = 33
LocalNumber = "766030703"
}
Email = "marcel.patulacci#outlook.com"
}
[<Fact>]
let ``Open an account...``() =
let event = Event.AccountOpened({
AccountId = id
Contact = contact
})
let a = [event]
let b = seq { yield event }
Assert.Equal(a, b)
System.NullReferenceException : Object reference not set to an instance of an object.
It seems surprising especially since considering that the overload used by Assert is:
public static void Equal<T>(IEnumerable<T> expected, IEnumerable<T> actual)
Which states that:
Verifies that two sequences are equivalent, using a default comparer.
Why are they considered different, and why does Assert.Equal raise a System.NullReferenceException?
[EDIT]
System.NullReferenceException : Object reference not set to an instance of an object.
at Domain.Events.AccountOpenedEvent.Equals(Object obj, IEqualityComparer comp)
at Domain.Events.Event.Equals(Object obj, IEqualityComparer comp)
Seems
type PersonalName = {
FirstName: string;
MiddleInitial: string option;
LastName: string;
}
type Address = {
Address1: string;
Address2: string option ;
City: string;
State: string option;
Zip: string;
}
type PhoneNumber = {
DialOutCode : int;
LocalNumber: string
}
type Contact = {
Name: PersonalName;
DateOfBith: DateTime
Email: string;
Address: Address;
PhoneNumber: PhoneNumber
}
type AccountOpenedEvent = {
AccountId: Guid
Contact: Contact
}
type Event =
| AccountOpened of AccountOpenedEvent
It turns out one of the fields of event was null, but not event itself.
The problem resided in the id and contact that were defined right above the test / [<Fact>]:
let id = Guid.Empty
let contact = {
Name = {
FirstName = "Marcel"
MiddleInitial = None
LastName = "Patulacci"
}
DateOfBith = new DateTime(1850, 12, 25)
Address = {
Address1 = "41 av 8 Mai 1945"
Address2 = None
City = "Sarcelles"
State = None
Zip = "95200"
}
PhoneNumber = {
DialOutCode = 33
LocalNumber = "766030703"
}
Email = "marcel.patulacci#outlook.com"
}
[<Fact>]
let ``Open an account...``() =
let event = Event.AccountOpened({
AccountId = id
Contact = contact
})
let a = [event]
let b = seq { yield event }
Assert.Equal(a, b)
The thing is when running the test independently the id and contact are not initialized, hence even though event was not null, contact was null (id being a Guid aka a struct it has a value anyway).
Since F# works with structural equality, if one of the field is not initialized it was enough to have a field null to make the Assert failed at some point in its implementation.
There are a few solutions / workarounds:
Defining those variables directly in the unit test body.
Defining methods which produce those values out of the unit test body
let getId() = Guid.Empty
let getContact() = {
Name = {
FirstName = "Marcel"
MiddleInitial = None
LastName = "Patulacci"
}
DateOfBith = new DateTime(1850, 12, 25)
Address = {
Address1 = "41 av 8 Mai 1945"
Address2 = None
City = "Sarcelles"
State = None
Zip = "95200"
}
PhoneNumber = {
DialOutCode = 33
LocalNumber = "766030703"
}
Email = "marcel.patulacci#outlook.com"
}
[<Fact>]
let ``Open an account...``() =
let id = getId()
let contact = getContact()
let event = Event.AccountOpened({
AccountId = id
Contact = contact
})
let a = [event]
let b = seq { yield event }
Assert.Equal(a, b)
While those workarounds work, I am surprised that the variables declared right above the unit test function are not considered when the test is running / and are uninitialized.
It might worth to shoot another question about why this is the case.
This is surprising in the sense that if a function can be defined and returning pretty much the same thing as those variables it means that let is also properly compiled, so why this is not the case with the variables?

Splitting a string after the last space

Iam making a program that syncs to a DB, and it takes in names, so i want to split the parsed in string into 2 strings after the last "space".
e.g. splitting the name "John Doe Jackson" into:
john Doe and Jackson
so far what ive done i getting the last name:
public static string getLastName(string fullname)
{
string lastName = fullname.Split(' ').LastOrDefault();
}
how to return the rest of the string so i have something like:
firstName: John Doe
lastName: Jackson
EDIT: made it by doing it like this, isnt the cleanest way, but it gets the job done!
public static string getLastName(string fullname)
{
string lastName = fullname.Split(' ').LastOrDefault();
Console.WriteLine(lastName);
return lastName;
}
public static string getFirstName(string fullname)
{
var parts = fullname.Split(' ');
var lastName = parts.LastOrDefault();
var firstName = string.Join(" ", parts.Take(parts.Length - 1));
return firstName;
}
You could try something like this:
var parts = fullname.Split(' ');
var lastName = parts.LastOrDefault();
var firstName = string.Join(" ",parts.Take(parts.Length-1));
I'd do:
var lastSpaceIndex = fullName.LastIndexOf(' ');
var firstName = fullName.Substring(0, lastSpaceIndex);
var lastName = fullName.Substring(lastSpaceIndex+1);
See it in action here
You can use string.Join
public static string getFirstName(string fullname)
{
return string.join(" ",fullname.Split(' ').Take(fullname.Split(' ').Count()-1));
}
Another version based on accepted answer but protecting for string null/empty and in case of no spaces in fullName leaving input as firstName
if (string.IsNullOrWhiteSpace(fullName))
return new KeyValuePair<string, string>(string.Empty, string.Empty);
var parts = fullName.Trim().Split(' ');
if (parts.Length == 1)
return new KeyValuePair<string, string>(parts[0], string.Empty);
var firstName = string.Join(" ", parts.Take(parts.Length - 1));
var lastName = parts.LastOrDefault();
return new KeyValuePair<string, string>(firstName, lastName);
string name = "John Doe Jackson";
var names = name.Split(' ');
string firstname = names[0] + " " + names[1];
string lastname = names[2];

How to get value from Dictionary to new Object in LINQ query

I have a Collection of Data and the Dictionary:
Collection handle Student Data
Dictionary Keeps Student Courses.
I want to get the Course Name from the Dictionary and put it into as CourseName.
private viod GenerateStudentDetails(Student studentData)
{
var courses = m_courses.GetCoursesDictionary(); // returns Dictionary<Guid,string>()
var studentDetails= from data in studentData
select new
{
FirstName = data.FirstName,
LastName = data.LastName,
Email = data.Email,
Mobile = data.Profile.Mobile,
City = data.Profile.City,
PostCode = data.Profile.PostCode,
CourseName = courses[data.Profile.CourseID ?? Guid.Empty]
};
}
"LINQ to Entities does not recognize the method 'System.String get_Item(System.Guid)' method, and this method cannot be translated into a store expression."
You could try something as the below:
private viod GenerateStudentDetails(Student studentData)
{
var courses = m_courses.GetCoursesDictionary(); // returns Dictionary<Guid,string>()
var studentDetails= (from data in studentData
select new
{
FirstName = data.FirstName,
LastName = data.LastName,
Email = data.Email,
Mobile = data.Profile.Mobile,
City = data.Profile.City,
PostCode = data.Profile.PostCode,
CourseID = data.Profile.CourseID
}).AsEnumerable()
.Select(item=>new
{
FirstName = item.FirstName,
LastName = item.LastName,
Email = item.Email,
Mobile = item.Profile.Mobile,
City = item.Profile.City,
PostCode = item.Profile.PostCode,
CourseName = courses[item.Profile.CourseID ?? Guid.Empty]
});
}
What's the problem?
The problem is that the last expression in the anonymous type you create,
CourseName = courses[data.Profile.CourseID ?? Guid.Empty]
cannot be in this place, because it can't be translated appropriately. So you have this option. You can declare a sequence of the data you want from the studentData and then make any conversion of call anything you want to the new anonymous type we create.
Just for thought
var eCourses = ((IEnumerable<KeyValuePair<Guid, string>>) courses);
var studentDetails = (from data in studentData
select new
{
data.FirstName,
data.LastName,
data.Email,
data.Profile.Mobile,
data.Profile.City,
data.Profile.PostCode,
CourseName = eCourses.FirstOrDefault(s => s.Key == data.Profile.CourseID).Value
});

Categories