Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I'm exporting my data to CSV, sometimes I'm exporting Products or Prices and sometimes I'm exporting Customers.
So right now I have 3 methods for each type, and I would like to own 1 method for example CSVExport instead of ArticlesCSVExport, CustomersCSVExport and so on..
public async Task<byte[]> ArticlesCSVExport(Request searchObject)
{
var columnHeaders = new string[]
{
"Article Name",
"Article Price",
"Article Type",
"Status"
};
var result = serviceMethod.GetAll(); // returning all articles..
var articles = (from Article in result
select new object[]
{ // Could get this values from column headers?
$"\"{Article.ArticleName}\"",
$"\"{Article.Price}\"",
$"\"{Article.ArticleType}\"",
$"{(Article.Active==true ? "Active" : "Inactive")}",
}).ToList();
var articlesCsv = new StringBuilder();
articles.ForEach(line =>
{
articlesCsv.AppendLine(string.Join(",", line));
});
byte[] buffer = Encoding.ASCII.GetBytes($"{string.Join(",", columnHeaders)}\r\n{articlesCsv.ToString()}");
return buffer;
}
I thought about extending parameters to receive maybe list of data for CSV, and maybe column headers? but than how could I escape proper value, because property names would be different for each class; maybe read it somehow from column headers?
In your code example, the only thing that would change if the class was Price or Customer instead of Article is this:
var articles = (from Article in result
select new object[]
{ // Could get this values from column headers?
$"\"{Article.ArticleName}\"",
$"\"{Article.Price}\"",
$"\"{Article.ArticleType}\"",
$"{(Article.Active==true ? "Active" : "Inactive")}",
}).ToList();
What changes is how you get an object[] from whatever the type is - Article, Product, etc.
It's not clear from the context whether you would want to make the class generic or the method generic. I'll demonstrate with the method, since the method is what you're showing. (It's also not clear where result and columnHeaders is declared.)
The signature would change to look like this:
public async Task<byte[]> ArticlesCSVExport<T>(
Request searchObject, Func<T, object[]> extractValuesFunction)
The generic argument T - allows the caller to specify what the type is (again Article, Product, etc.)
This next argument:
Func<T, object[]> extractValuesFunction
...represents passing in a function that takes an instance of T and returns a List<object>. In other words, instead of that code being part of the method, you're passing it as a parameter to the method.
Now you can replace that section of code with:
var lineElements = result.Select(item => extractValuesFunction(item)).ToList();
or simplify to
var lineElements = result.Select(extractValuesFunction).ToList();
I named the variable lineElements (?) instead of articles because now they could be anything, not just articles.
What we're saying is, "For each one of these things, execute this function which will convert it to an array of objects."
All the parts of the method that stay the same regardless of what T is are still part of the method. Whatever changes is moved outside of the method and passed in as an argument. Your original method would now look like this:
public async Task<byte[]> ArticlesCSVExport<T>(
Request searchObject, Func<T, object[]> extractValuesFunction)
{
var lineElements = result.Select(extractValuesFunction).ToList();
var csv = new StringBuilder();
lineElements.ForEach(line =>
{
csv.AppendLine(string.Join(",", line));
});
byte[] buffer = Encoding.ASCII.GetBytes($"{string.Join(",", columnHeaders)}\r\n{csv.ToString()}");
return buffer;
}
(This also assumes that result is a collection of T instead of a collection of Article, but that's unclear because result isn't declared in this method.)
Now, instead of having inline code saying how to take an Article and return an object[], you'll just execute that function for each item.
Calling the function could look like this:
var output = await ArticlesCSVExport<Article>(
searchObject,
article =>
new object[]
{
$"\"{article.ArticleName}\"",
$"\"{article.Price}\"",
$"\"{article.ArticleType}\"",
$"{(article.Active == true ? "Active" : "Inactive")}"
});
In that example we're passing in an anonymous function, but we can pass any function that has the correct signature.
Suppose we have a class like this with a static method:
public static class CsvFormatFunctions
{
public static object[] GetArticleValues(Article article)
{
return new object[]
{
$"\"{article.ArticleName}\"",
$"\"{article.Price}\"",
$"\"{article.ArticleType}\"",
$"{(article.Active == true ? "Active" : "Inactive")}"
});
}
}
...then we could pass that method as a parameter:
var output = await ArticlesCSVExport<Article>(
searchObject, CsvFormatFunctions.GetArticleValues);
Depending on details that I can't see, the class might need to be generic instead of the method. If that's the case then you would just remove the generic argument <T> from the method and put it on the class declaration instead.
Best way is to use strategy design pattern
interface IParser
{
Task<byte[]> Parse(Request searchObject);
}
public class CustomersCSVExport : IParser
{
public Task<byte[]> Parse(Request searchObject)
{
throw new System.NotImplementedException();
}
}
public class ArticlesCSVExport : IParser
{
public async Task<byte[]> Parse(Request searchObject)
{
// Defining file headers
var columnHeaders = new string[]
{
"Article Name",
"Price",
"Type",
"Status"
};
// Get the data from Article Service to export/download
var result = await ArticleService.Get(searchObject);
// Escaping ","
var articles = (from Article in result
select new object[]
{ // Could get this values from column headers?
$"\"{Article.ArticleName}\"",
$"\"{Article.Price}\"",
$"\"{Article.ArticleType}\"",
$"{(Article.Active==true ? "Active" : "Inactive")}",
}).ToList();
// Build the file content
var articlesCsv = new StringBuilder();
articles.ForEach(line =>
{
articlesCsv.AppendLine(string.Join(",", line));
});
byte[] buffer = Encoding.ASCII.GetBytes($"{string.Join(",", columnHeaders)}\r\n{articlesCsv.ToString()}");
return buffer;
}
}
class CsvParser
{
private IParser _parser;
public void SetParser(IParser parser)
{
_parser = parser;
}
public Task<byte[]> Parse(Request searchObject)
{
return _parser.Parse(searchObject);
}
}
class Client{
void Main()
{
var csvParser= new CsvParser();
csvParser.SetParser(new ArticlesCSVExport());
var articleResult =csvParser.Parse(new Request());
csvParser.SetParser(new CustomersCSVExport());
var customerResult = csvParser.Parse(new Request());
}
}
Try to see if you can parse using expressions. The mapping can be supplied by the user as mentioned in the example here: https://www.codeproject.com/Articles/685310/Simple-and-Fast-CSV-Library-in-Csharp
You can make a generic method, that works independendly of the type.
To define what columns are written, and what value is written for an item, you could require an array of column definitions.
Each column definition would contain the header of that column, and the means how to get the value for an item.
Then just build the header line, and for each item the values line.
Example:
public static string ToCsv<T>(this IEnumerable<T> items, params (string title, Func<T, string> valueProvider)[] columnDefinitions)
{
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
if (columnDefinitions == null || columnDefinitions.Length == 0)
{
throw new ArgumentException(nameof(columnDefinitions));
}
var builder = new StringBuilder();
foreach (var cd in columnDefinitions)
{
var title = cd.title;
builder.Append(PrepareCsvValue(cd.title));
builder.Append(',');
}
builder.AppendLine();
foreach (var item in items)
{
foreach (var cd in columnDefinitions)
{
builder.Append(PrepareCsvValue(cd.valueProvider(item)));
builder.Append(',');
}
builder.AppendLine();
}
return builder.ToString();
}
private static string PrepareCsvValue(string value)
{
value = value.Replace("\"", "\"\"");
if (value.Contains(','))
{
value = $"\"{value}\"";
}
return value;
}
Here is an example on how to use it:
public static void Example()
{
var items = new TimeSpan[]
{
new TimeSpan(0),
new TimeSpan(0, 0, 0, 1),
new TimeSpan(0, 0, 1, 1),
new TimeSpan(0, 1, 1, 1),
new TimeSpan(1, 1, 1, 1)
};
var csvText = items.ToCsv(
("Days", ts => ts.Days.ToString()),
("Hours", ts => ts.Hours.ToString()),
("Minutes", ts => ts.Minutes.ToString()),
("Seconds", ts => ts.Seconds.ToString()));
// Do something with the csv text.
}
This is just to get the csv text.
You can create another method that also exports it to a file, using the first method.
Note:
I used named types for the column definitions.
That makes for a nice formating in the method call, but can be confusing to some.
You can find more information on named tuples here.
Note 2:
I added code that handles special characters inside values.
As per specification, values may use delimiters, if they are surounded by the " character.
Existing " characters need to be escaped with another ", thus (a"b -> a""b).
I have simple C# ArrayList:
ArrayList sapTestData;
sapTestData = new ArrayList();
and I add elements with:
sapTestData.Add(new { Value = loanId, Text = description });
Yet when I try to access sapTestData, with
string test = sapTestData[1].Value;
I get a 'object' does not contain a definition for 'Value'.... error...
What is problem?
You can simply use generics.
Have an object like this:
var Loan = new { Value = “Loan1”, Text = description };
Add to list like this:
var loanList = GetLoanList(Loan);
Access it like below:
if(loanList.Any(n => n.Value == "Loan1"))
{
Console.WriteLine("Loan 1 is there");
}
Have a method like below:
public List<T> GetLoanList<T>(T item)
{
List<T> newLoanList = new List<T>();
newLoanList.Add(item);
return newLoanList;
}
var is a great feature in C# but sometimes in code like these inference is desirable.
This question already has answers here:
Sorting of list contained strings having alphabetic/numeric
(2 answers)
Closed 8 years ago.
I have a class with one property "Name" containing names like "1_[AnnualRevenue]","2_[ResellerType]","3_xxx"....
my class is like
class xxx
{
private string fileName;
public string FileName
{
get { return fileName; }
set { fileName = value; }
}
}
And I am assigning the values to the object of the class. like xxx.FileName="1_[AnnualRevenue]";
Now I have a list class. And now sort the list according to this class property.
Now I want to sort the field according to the numeric order, I mean 1 first 2 second and so on.
And then write it to filestream.
Could any body help me with this.
Thanks in advance.
Since the property is a String but you want to sort it numerically, probably the best way would be to implement IComparable on your class and then put your custom sort code in the CompareTo method. Then you don't have to write a more complex Lambda statement each time you want to Sort a list, you can just call the Sort() method on the list.
You can also handle cases where the FileName property does not contain an underscore or is null, rather than getting exceptions in your OrderBy code (which is what would happen with most of the other answers).
I made a couple of other changes also - override the ToString method so you can easily display the value to the console window, and used Automatic property syntax for the FileName property so we can remove the backing field:
class xxx : IComparable<xxx>
{
public string FileName { get; set; }
public int CompareTo(xxx other)
{
// Short circuit if any object is null, if the
// Filenames equal each other, or they're empty
if (other == null) return 1;
if (FileName == null) return (other.FileName == null) ? 0 : -1;
if (other.FileName == null) return 1;
if (FileName.Equals(other.FileName)) return 0;
if (string.IsNullOrWhiteSpace(FileName))
return (string.IsNullOrWhiteSpace(other.FileName)) ? 0 : -1;
if (string.IsNullOrWhiteSpace(other.FileName)) return 1;
// Next, try to get the numeric portion of the string to compare
int thisIndex;
int otherIndex;
var thisSuccess = int.TryParse(FileName.Split('_')[0], out thisIndex);
var otherSuccess = int.TryParse(other.FileName.Split('_')[0], out otherIndex);
// If we couldn't get the numeric portion of the string, use int.MaxValue
if (!thisSuccess)
{
// If neither has a numeric portion, just use default string comparison
if (!otherSuccess) return FileName.CompareTo(other.FileName);
thisIndex = int.MaxValue;
}
if (!otherSuccess) otherIndex = int.MaxValue;
// Return the comparison of the numeric portion of the two filenames
return thisIndex.CompareTo(otherIndex);
}
public override string ToString()
{
return FileName;
}
}
Now, you can just call Sort on your list:
List<xxx> list = new List<xxx>
{
new xxx {FileName = "13_a"},
new xxx {FileName = "8_a"},
new xxx {FileName = null},
new xxx {FileName = "1_a"},
new xxx {FileName = "zinvalid"},
new xxx {FileName = "2_a"},
new xxx {FileName = ""},
new xxx {FileName = "invalid"}
};
list.Sort();
Console.WriteLine(string.Join("\n", list));
// Output (note the first two are the empty string and the null value):
//
//
// 1_a
// 2_a
// 8_a
// 13_a
// invalid
// zinvalid
You can use LINQ to do that for you
List<xxx> orderedList = unOrderedList.OrderBy(o => Convert.ToInt32(o.FileName.Split('_').First())).ToList();
Editted the answer on behalf of the comments - pointing out that indeed we need to convert to integers to order correctly.
You can do like following to sort the list:
List<xxx> list = new List<xxx>
{
new xxx { FileName = "3_a" },
new xxx { FileName = "1_a" },
new xxx { FileName = "2_a" },
new xxx { FileName = "8_a" }
};
var sorted = list.OrderBy(it => Convert.ToInt32(it.FileName.Split('_')[0]));//using System.Linq;
And you can write the list to disk file as below:
using (TextWriter tw = new StreamWriter("C:\\FileNames.txt"))
{
foreach (var item in sorted)
{
tw.WriteLine(item.FileName.ToString());
}
}
This question already has answers here:
LINQ return items in a List that matches any Names (string) in another list
(5 answers)
Closed 8 years ago.
To make my long story short i had to improvise this code a little:
public class Faerie
{
public string Name;
}
public class Example
{
List<Faerie> faeries = new List<Faerie>() {
new Faerie { Name = "Wild Faerie" } ,
new Faerie { Name = "Smoke Faerie" },
new Faerie { Name = "Red Faerie" }
};
string[] faerieNamesFromInput = new string[] { "White Faerie", "Wild Faerie", "Dark Faerie" };
public Faerie ReturnMatchedFromInput()
{
}
}
How can i return a Fairy object from the fairies list if its name matches a name from the user input? Like for instance, here i want to return the Faerie with name Wild Faerie because it's name matches.Is there a short LINQ way for that or i have to go with for loop?
If you want to return multiple matches
faeries.Where(x => faerieNamesFromInput.Contains(x.Name));
If you want to return the first matched then
faeries.FirstOrDefault(x => faerieNamesFromInput.Contains(x.Name));
Simply do
var result = faeries.FirstOrDefault(x => faerieNamesFromInput.Contains(x.Name));
Make sure to include System.LINQ namespace.
This question already has answers here:
Get property value from string using reflection
(24 answers)
Closed 8 years ago.
I am pulling several elements of data out of an object in C#. They are all in the same 'place' within the object, as in:
objectContainer.TableData.Street.ToString());
objectContainer.TableData.City.ToString());
objectContainer.TableData.State.ToString());
objectContainer.TableData.ZipCode.ToString());
I would LIKE to use a foreach loop to pull them all and be able to add more by adding to the array.
string[] addressFields = new string[] { "Street", "City", "State", "ZipCode" };
foreach(string add in addressFields)
{
objectContainer.TableData.{add}.ToString());
}
Can this be done, and if so, what is the correct procedure?
You would need to use reflection to achieve this:
var type = objectContainer.TableData.GetType();
foreach(var addressFieldName in addressFieldNames)
{
var property = type.GetProperty(addressFieldName);
if(property == null)
continue;
var value = property.GetValue(objectContainer.TableData, null);
var stringValue = string.Empty;
if(value != null)
stringValue = value.ToString();
}
Please note: This code is pretty defensive:
It will not crash if no property with the specified name exists.
It will not crash if the value of the property is null.
You can use Reflection to do this.
string[] addressFields = new string[] { "Street", "City", "State", "ZipCode" };
foreach(string add in addressFields)
{
var myVal = objectContainer.TableData.GetType().GetProperty(add).GetValue(objectContainer.TableData).ToString();
}
Note that this doesn't allow for array values that don't have a corresponding property on objectContainer.TableData.