I have a base class ProductDetails which is responsible for parsing the productstring, there is another class SelfManufactured which inherits from the baseclass ProductDetails, there is another class - ThirdPartyProduct , which inherits from the SelfManufactured class and has additional details which are specific to this class, like ThirdPartyName, ThirdPartyAddress.
a) For SelfManufactured product the string should be - prefixed with 0
b) For ThirdPartyProduct product the string should be - prefixed with 1
workflow goes like this:
I create a ThirdPartyProduct object using following code:
private void ThirdParty_Click(object sender, RoutedEventArgs e)
{
ThirdPartyProduct thirdparty = new ThirdPartyProduct ("1000", "200", "PROD1",
"XYZW", "ABCD");
string thirdpartyProductString = thirdparty.ToString();
}
if you check the variable thirdpartyProductString in below code it is : appending 0 and then 1.
It appends 0 and then 1 instead what I would like to have is only "1" getting prefixed.
I guess since SelfManufactured class is also called while creating ThirdParty object, it keeps appending values, I would like to have a way that ToString() does not append values when not needed.
ProductDetails.cs
using System;
namespace product
{
public class ProductDetails
{
private readonly string _productString;
public string ProductString
{
get => ToString();
}
public ProductDetails(string productId, string productName, string productLot)
{
_productString = // somevalue here....
}
public override string MyString()
{
return _productString;
}
}
}
SelfManufactured.cs
public class SelfManufactured : ProductDetails
{
public override string MyString()
{
string str = string.Empty;
str = "additional value of 0" + // append the identifier as '0' with product string
return str;
}
}
ThirdPartyProduct.cs
public class ThirdPartyProduct : SelfManufactured
{
public string ThirdPartyName { get; set; }
public string ThirdPartyAddress { get; set; }
public override string MyString()
{
string str = string.Empty;
// append the identifier as '1' with product string and add additional details with the string.
return str;
}
}
Since you are calling base.ToString() in "ThirdPartyProduct" class, it obviously calls the base class ToString() method and it adds "0" to it and making the final string as "101000200PROD1XYZWABCD"
If you don't want to do that then you can refer "ProductString" property of ProductDetails class directly from ToString() method of ThirdPartyProduct class. See below my changes
ProductDetails.cs - I am returning the private read-only from here.
public string ProductString
{
get { return _productString; }
}
Then the ToString() method of ThirdPartyProduct.cs class made to following changes
public override string ToString()
{
string str = string.Empty;
// append the identifier as '0' with product string and add additional details with the string.
str = (int)ProductType.ThirdParty + ProductString + ThirdPartyName + ThirdPartyAddress;
return str;
}
Made changes at the following line -
str = (int)ProductType.ThirdParty + base.ToString() + ThirdPartyName + ThirdPartyAddress;
to
str = (int)ProductType.ThirdParty + ProductString + ThirdPartyName + ThirdPartyAddress;
Note:- I hope always the ThirdPartyProduct class should not append "0", then above solution should be fine, else you need to think of other approach for the calculations.
You already have a property in class ProductDetails called "TypeOfProduct". So in SelfManufactured.ToString(), instead of hard-coding the product type, use the property's value:
str = (int)TypeOfProduct + base.ToString();
Then, in ThirdPartyProduct, there's no need to use the product type as this is taken care of in the base class. So instead call base.ToString() followed by the class-specific details:
str = base.ToString() + ThirdPartyName + ThirdPartyAddress;
Related
Consider the following;
public override string ToString()
{
return $"{HouseName}{StreetLine1 + " : "}{StreetLine2}{PostalTown + " : "}:{PostCode + " : "}{Country}";
}
This is nothing more than a simple override of ToString() to give end users in an application a little more meaningful information about an address entity than they would otherwise get were no override provided.
HouseName, StreetLine2 and Country are all allowed null values on the backend database. I am wondering if rather than writing separate methods to determine the value of these and then return either nothing or the value + " : " there is a way to do this via a Lambda or func within the actual string interpolation statement itself.
I am still learning my way around C# and what searching I have done to date seems to indicate that this probably isn't possible even with the magical Elvis operator. However it's equally possible that I've simply misunderstood what I have been reading.
EDIT
Following #Evk's answer I created the following quick console app.
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var adr = new Address { StreetLine1 = "1 Any Road", PostalTown = "Any Town", PostCode = "AB12 3CD" };
Console.WriteLine($"{(adr.HouseName != null ? " : " + adr.HouseName : "")} : {adr.StreetLine1 } : { (adr.StreetLine2 != null ? " : " + adr.StreetLine2 : "")} : {adr.PostalTown} : {adr.PostCode} ");
Console.ReadLine();
}
}
public class Address
{
public string HouseName { get; set; }
public string StreetLine1 { get; set; }
public string StreetLine2 { get; set; }
public string PostalTown { get; set; }
public string PostCode { get; set; }
public string Country { get; set; }
}
}
This produced the following result
: 1 Any Road : : Any Town : AB12 3CD
In reality I was after
1 Any Road : Any Town : AB12 3CD
and as you can see I have not even factored in Country , which had it been set should have produced;
1 Any Road : Any Town : AB12 3CD : Any Country
if rather than writing separate methods to determine the value of
these and then return either nothing or the value + " : " there is a
way to do this within the actual string interpolation statement itself
You can use "?:" operator, just enclose it in "()":
$"{StreetLine1 + (StreetLine2 != null ? " : " + StreetLine2 : "")}";
However if you just need to join bunch of strings together - use String.Join:
String.Join(" : ", new[] {HouseName, StreetLine1, StreetLine2}.Where(c => c != null));
I am in middle of writing an assignment for my part time programming course. The issues with my code is the array.find() and results of that search. It should(In my theory) search the array for information and then post them to the user, however what comes out from all of the searches is the same thing: ass2task1.Program+customer Here are only parts of the code becouse out teacher told us we can post question on the internet as long as we don't post our entire codes
struct customer
{
public int customernumber;
public string customersurname;
public string customerforname;
public string customerstreet;
public string customertown;
public DateTime customerdob;
}
static void Main(string[] args)
{
customer[] customerdetails = new customer[99];
int selector = 0;
int selector2 = 0;
string vtemp = "";
string ctemp = "";
int searchnumber;
string searchforename; //variable/ array declaring
string searchsurname;
string searchtown;
DateTime searchdob;
customer resultnumber;
customer resultforename;
customer resultsurname;
customer resulttown;
customer resultdob;
if (selector2 == 2)
{
Console.Clear();
Console.WriteLine("Enter the forename you are looking for: ");
searchforename = (Console.ReadLine());
resultforename = Array.Find(customerdetails, customer => customer.customerforname == searchforename);
Console.Clear();
Console.WriteLine("Enter the surname you are looking for: "); // all of the searches comes out with ass2task1.Program+customer result
searchsurname = (Console.ReadLine());
resultsurname = Array.Find(customerdetails, customer => customer.customersurname == searchsurname);
Console.WriteLine("The forename resuts:" + resultforename);
Console.WriteLine("The surname resuts:" + resultsurname);
Array.Find() will return the object that matches a predicate, if you want the property value, you would need to do something like: resultforename.customerforname or something similar.
If it is not found, then a default value will be returned, so check for nulls etc.
When you convert an object to a string ("The forename resuts:" + resultforename) it calls the objects ToString() method. Define an appropriate ToString() method:
struct customer
{
public int customernumber;
public string customersurname;
public string customerforname;
public string customerstreet;
public string customertown;
public DateTime customerdob;
public override string ToString()
{
return customersurname + ", " + customerforname;
}
}
To (attempt to) expand on Ric and clcto's answers. The reason you're getting the struct name in your
Console.WriteLine("The forename resuts:" + resultforename);
Console.WriteLine("The surname resuts:" + resultsurname);
Your resultforename is of struct customer - by default Console.WriteLine(struct) does not know how to represent a complex object as a string.
As suggested you could do
Console.WriteLine("The forename resuts:" + resultforename.customerforname);
Or provide your own .ToString() method for the struct as clcto pointed out - doing this you're basically telling Console.WriteLine (or any string representation) how to represent a struct of customer as string.
Don't know if this will help, or make it even less clear. But given:
public struct Foo
{
public string Bar { get; set; }
}
public struct FooTwo
{
public string Bar { get; set; }
public override string ToString()
{
return "This is how to represent a Foo2 as string: " + Bar;
}
}
Foo[] foos = new Foo[99];
Foo foundFoo = foos[0]; // This is equivalent to your find statement... setting a foundFoo local variable to a Foo struct
string foundBar = foos[0].Bar; // This is another way to get to what you're trying to accoomplish, setting a string value representation of your found object.
Console.WriteLine(foundFoo); // Doesn't know how to deal with printing out a Foo struct - simply writes [namespace].Foo
Console.WriteLine(foundFoo.Bar); // Outputs Foo.Bar value
Console.WriteLine(foundBar); // Outputs Foo.Bar value
FooTwo foo2 = new FooTwo();
foo2.Bar = "test bar";
Console.WriteLine(foo2); // outputs: "This is how to represent a Foo2 as string: test bar"
I have a string with the following structure:
Student Name________AgeAddress_______________________Bithday___Lvl
Example:
Jonh Smith 016Some place in NY, USA 01/01/2014L01
As you can see, there is no delimited character like | or ,
Also, there is no space between fields (if you check, there is no space between Age/Address and Birthday/Level.
The size of each field is static so if data's length is less then it will contains white spaces.
I have a class that need to be filled with that information:
public class StudentData
{
public char[] _name = new char[20];
public string name;
public char[] _age = new char[3];
public string age;
public char[] _address = new char[30];
public string address;
public char[] _bday = new char[10];
public string bday;
public char[] _level = new char[3];
public string level;
}
Is there any way to do this automatically and dynamically?
I mean I really don't want to code like this:
myClass.name = stringLine.substring(0,19);
myClass.age = stringLine.substring(20,22);
That's because I have way more fields that the ones added in this example & way more string lines with other different data.
Update: There were supposed to be a lot of spaces between "Smith" and "016", but I don't know how to edit it.
Update2: If I use StringReader.Read() I can evade to use substring and indexes, but it isn't still so dynamically because I would need to repeat those 3 lines for each field.
StringReader reader = new StringReader(stringLine);
reader.Read(myClass._name, 0 myClass._name.Length);
myClass.name = new string(myClass._name);
Given your requirement I came up with an interesting solution. All be-it it may be more complex and longer than using the String.SubString() method as stated.
However this solution is transferable to other types and other string. I used a concept of Attributes, Properties, and Reflection to parse a string by a Fixed Length and setting the class Properties.
Note I did change your StudentData class to follow a more conventional coding style. Following this handy guide on MSDN: http://msdn.microsoft.com/en-us/library/xzf533w0(v=vs.71).aspx
Here is the new StudentData class. Note it uses the properties as opposed to fields. (Not discussed here).
public class StudentData
{
string name;
string age;
string address;
string bday;
string level;
[FixedLengthDelimeter(0, 20)]
public string Name { get { return this.name; } set { this.name = value; } }
[FixedLengthDelimeter(1, 3)]
public string Age { get { return this.age; } set { this.age = value; } }
[FixedLengthDelimeter(2, 30)]
public string Address { get { return this.address; } set { this.address = value; } }
[FixedLengthDelimeter(3, 10)]
public string BDay { get { return this.bday; } set { this.bday = value; } }
[FixedLengthDelimeter(4, 3)]
public string Level { get { return this.level; } set { this.level = value; } }
}
Note on each of the properties there is an Attribute called FixedLengthDelimeter that takes two parameters.
OrderNumber
FixedLength
The OrderNumber parameter denotes the order in the string (not the position) but the order in which we process from the string. The second parameter denotes the Length of the string when parsing the string. Here is the full attribute class.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class FixedLengthDelimeterAttribute : Attribute
{
public FixedLengthDelimeterAttribute(int orderNumber, int fixedLength)
{
this.fixedLength = fixedLength;
this.orderNumber = orderNumber;
}
readonly int fixedLength;
readonly int orderNumber;
public int FixedLength { get { return this.fixedLength; } }
public int OrderNumber { get { return this.orderNumber; } }
}
Now the attribute is simple enough. Accepts the two paramters we discussed eariler in the constructor.
Finally there is another method to parse the string into the object type such as.
public static class FixedLengthFormatter
{
public static T ParseString<T>(string inputString)
{
Type tType = typeof(T);
var properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public); //;.Where(x => x.GetCustomAttributes(typeof(FixedLengthDelimeterAttribute), false).Count() > 0);
T newT = (T)Activator.CreateInstance(tType);
Dictionary<PropertyInfo, FixedLengthDelimeterAttribute> dictionary = new Dictionary<PropertyInfo, FixedLengthDelimeterAttribute>();
foreach (var property in properties)
{
var atts = property.GetCustomAttributes(typeof(FixedLengthDelimeterAttribute), false);
if (atts.Length == 0)
continue;
dictionary[property] = atts[0] as FixedLengthDelimeterAttribute;
}
foreach (var kvp in dictionary.OrderBy(x => x.Value.OrderNumber))
{
int length = kvp.Value.FixedLength;
if (inputString.Length < length)
throw new Exception("error on attribute order number:" + kvp.Value.OrderNumber + " the string is too short.");
string piece = inputString.Substring(0, length);
inputString = inputString.Substring(length);
kvp.Key.SetValue(newT, piece.Trim(), null);
}
return newT;
}
}
The method above is what does the string parsing. It is a pretty basic utility that reads all the properties that have the FixedLengthDelimeter attribute applied a Dictionary. That dictionary is then enumerated (ordered by OrderNumber) and then calling the SubString() method twice on the input string.
The first substring is to parse the next Token while the second substring resets the inputString to start processing the next token.
Finally as it is parsing the string it is then applying the parsed string to the property of the class Type provided to the method.
Now this can be used simply like this:
string data1 = "Jonh Smith 016Some place in NY, USA 01/01/2014L01";
StudentData student = FixedLengthFormatter.ParseString<StudentData>(data1);
What this does:
Parses a string against property attributes in a fixed length format.
What this does not do:
It does convert the parsed strings to another type. Therefore all the properties must be a string. (this can be easily adapted by adding some type casting logic in).
It is not well tested. This is only tested against a few samples.
It is not by all means the only or best solution out there.
You could use FileHelpers library (NuGet).
Just define the structure of your input file with attributes:
[FixedLengthRecord]
public class StudentData
{
[FieldFixedLength(20)]
[FieldTrim(TrimMode.Right)]
public string name;
[FieldFixedLength(3)]
public string age;
[FieldFixedLength(30)]
[FieldTrim(TrimMode.Right)]
public string address;
[FieldFixedLength(10)]
public string bday;
[FieldFixedLength(3)]
public string level;
}
Then simply read the file using FileHelperEngine<T>:
var engine = new FileHelperEngine<StudentData>();
var students = engine.ReadFile(filename);
I am using properties to allow a single string name to be added to the class Details, I want the property to only accept the string if it can be split into two parts.
The two parts would be the firstName and the LastName. However if the resulting split has 1,0 or more than 2 strings in the array then the input should be deemed invalid and I want to throw a error to whatever code called the property in the first place.
Can error handling be done on properties like this?
If not then what of the following is the preferred way to get data into a class whilst checking for correctness:
Use a method inside the class Details to handle error inputs, make that method boolean.
Continue using properties but have the error checking done by the code that calls the property. I don't like this because I want all the error checking code to be self-contained in the Details class.
.
class Details
{
private string firstName, lastName;
public string Name
{
// name
get { return firstName + " " + lastName; }
set
{
string name = value;
string[] nameArray = name.Split(' ');
firstName = nameArray[0];
lastName = nameArray[1];
}
}
}
EDIT: I am mostly interested in what of the three options is concidered best pratice:
Error check inside properties.
Error check outside class in another class, and then just add the verified inputs to Details
Use a boolean method inside Details to verify the inputs.
I would follow an existing validation framework, such as FluentValidation.
Also, in your specific case, I would have a SetName(string fullName) method that does the parsing and populating.
Why not use exception to capture the error condition.
private string firstName, lastName;
public string Name
{
get { return string.Concat(firstName, " ", lastName); }
set
{
string name = value;
if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException("Name"); }
var nameParts = name.Trim().Split(' ');
if (nameParts.Length != 2) { throw new ArgumentException("Invalid name value"); }
firstName = nameParts[0];
lastName = nameParts[1];
}
}
Just to add my perspective to what others have said, it seems to me that part of the difficulty stems from the fact that you're using a property setter to do something non-trivial (split a string, validate the results, and then store the results in two fields). Generally, properties, especially read/write properties, should only be concerned with getting and setting a single value (using read-only properties to return a simple computed value is common enough too).
If it were me, I'd have separate properties for the first name and last name, with a third computed property for the full name:
class Details
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get { return string.Concat(this.FirstName, " ", this.LastName); }
}
}
Then you could add a method to set the full name. A method is more appropriate than a property setter for this task, since it's non-trivial and has more potential for problems:
public void SetFullName(string fullName)
{
string[] nameComponents = fullName.split(' ');
if (nameComponents.Length != 2)
{
throw new ArgumentException("The full name must contain a first and last name.");
}
this.FirstName = nameComponents[0];
this.LastName = nameComponents[1];
}
I also want to give a plug for the Code Contracts package. It may be more complication than you're looking for here, but it's a great way of validating input and output in your applications.
So I wouldn't consider data that doesn't meet business logic an exception and thus wouldn't throw one. What I would do is this:
class Details
{
private string firstName, lastName;
public string Name
{
// name
get { return firstName + " " + lastName; }
set
{
string name = value;
string[] nameArray = name.Split(' ');
if(nameArray.Length == 2)
{
firstName = nameArray[0];
lastName = nameArray[1];
}
else
{
firstName = nameArray[0];
lastName = string.Empty;
}
}
}
public bool IsValid()
{
return !string.IsNullOrEmpty(lastName);
}
}
You can then use the name property and then check to see if the name is valid. If not valid, then you can take the appropriate action.
Another option would be to have the validation done in the method calling Details.Name.
EDIT: want to remove what I think is bad advice but keeping so comments make sense, so just striking them out
EDIT2:
You could also do something like this:
class Details
{
private string firstName, lastName;
public string Name
{
get { return firstName + " " + lastName; }
private set;
}
public bool TryParseName(string name)
{
bool isValid = true;
string[] nameParts = name.split(' ');
if(nameParts.Length == 2)
{
firstName = nameParts[0];
lastName = nameParts[1];
}
else
{
isValid = false;
}
return isValid;
}
}
Where you would do
if(details.TryParseName(name))
{
// is valid name
}
else
{
// handle invalid name
}
This question already has answers here:
Enum ToString with user friendly strings
(25 answers)
Using [Display(Name = "X")] with an enum. Custom HtmlHelper in MVC3 ASP.Net
(3 answers)
Closed 9 years ago.
Tried setting up an enum that has spaces in attributes but was very hard so figured their might be an easy way to hack this with a string format or something since their is only one enum that I need that has spaces and I know exactly what it is. Any helping wiht adding a space to this string
public class Address
{
...blah...more class datatypes here...
public AddressType Type { get; set; } //AddressType is an enum
...blah....
}
if (Address.Type.ToString() == "UnitedStates")
{
**Add space between United and States**
}
If your enum entries are in camel case, you can insert a whitespace before the upper letter
string res = Regex.Replace("UnitedStates", "[A-Z]", " $0").Trim();
You can create your own ToString method on the enumeration using an extension method.
public static class AddressTypeExtensions
{
public static string ToMyString(this AddressType addressType)
{
if (addressType == AddressType.UnitedStates)
return "United States";
return addressType.ToString();
}
}
This is a neat trick I found yesterday (in 2009). I wonder why I never thought of it myself. In the .net framework there is no way how to control .ToString() for enumerations. To work around that an extension method can be created as well as an attribute to decorate the different values of the enumeration. Then we can write something like this:
public enum TestEnum
{
[EnumString("Value One")]
Value1,
[EnumString("Value Two")]
Value2,
[EnumString("Value Three")]
Value3
}
EnumTest test = EnumTest.Value1;
Console.Write(test.ToStringEx());
The code to accomplish this is pretty simple:
[AttributeUsage(AttributeTargets.Field)]
public class EnumStringAttribute : Attribute
{
private string enumString;
public EnumStringAttribute(string EnumString)
{
enumString = EnumString;
}
public override string ToString()
{
return enumString;
}
}
public static class ExtensionMethods
{
public static string ToStringEx(this Enum enumeration)
{
Type type = enumeration.GetType();
FieldInfo field = type.GetField(enumeration.ToString());
var enumString =
(from attribute in field.GetCustomAttributes(true)
where attribute is EnumStringAttribute
select attribute).FirstOrDefault();
if (enumString != null)
return enumString.ToString();
// otherwise...
return enumeration.ToString();
}
}
[TestMethod()]
public void ToStringTest()
{
Assert.AreEqual("Value One", TestEnum.Value1.ToStringEx());
Assert.AreEqual("Value Two", TestEnum.Value2.ToStringEx());
Assert.AreEqual("Value Three", TestEnum.Value3.ToStringEx());
}
The credit goes to this post.
I have a handy Extension method for exactly this
public static class EnumExtensions
{
public static string ToNonPascalString(this Enum enu)
{
return Regex.Replace(enu.ToString(), "([A-Z])", " $1").Trim();
}
public T EnumFromString<T>(string value) where T : struct
{
string noSpace = value.Replace(" ", "");
if (Enum.GetNames(typeof(T)).Any(x => x.ToString().Equals(noSpace)))
{
return (T)Enum.Parse(typeof(T), noSpace);
}
return default(T);
}
}
Example:
public enum Test
{
UnitedStates,
NewZealand
}
// from enum to string
string result = Test.UnitedStates.ToNonPascalString(); // United States
// from string to enum
Test myEnum = EnumExtensions.EnumFromString<Test>("New Zealand"); // NewZealand
The following code will convert AbcDefGhi to Abc Def Ghi.
public static string ConvertEnum(this string value)
{
string result = string.Empty;
char[] letters = value.ToCharArray();
foreach (char c in letters)
if (c.ToString() != c.ToString().ToLower())
result += " " + c;
else
result += c.ToString();
return result;
}