Creating one Function that can handle multiple return types in C#
I am relatively new to C#, having done most of my previous programming in VB6. I know C# is much more explicitly typed than VB, but I'm hoping that there is a solution for my problem.
I am working on a project that is designed to open, parse, validate, and eventually edit 5 different CSV files that are used as input to an application we use. Manually manipulation of the CSV files is what is done now, but it is difficult for most users because of lack of documentation and suppo开发者_JAVA技巧rt from the original developer. My goal is to build a GUI that will permit users to directly edit the fields and create a new CSV file to use as an import. Here's the basic structure now:
public class Dataset
{
public Dictionary<string,File1> file1 = new Dictionary<string,File1>();
public Dictionary<string,File2> file2 = new Dictionary<string,File2>();
public Dictionary<string,File3> file3 = new Dictionary<string,File3>();
public Dictionary<string,File4> file4 = new Dictionary<string,File4>();
public Dictionary<string,File5> file5 = new Dictionary<string,File5>();
public void SelectFiles()
{
//User specifies which file(s) are to be opened (default is all 5 files)
}
public class File1
{
public string Field_1 {get ; set}
public string Field_2 {get ; set}
.
.
.
public string Field_10 {get ; set}
}
public class File2
{
public string Field_1 {get ; set}
public string Field_2 {get ; set}
.
.
.
public string Field_31 {get ; set}
}
public class File3
{
public string Field_1 {get ; set}
public string Field_2 {get ; set}
.
.
.
public string Field_57 {get ; set}
}
public class File4
{
public string Field_1 {get ; set}
public string Field_2 {get ; set}
.
.
.
public string Field_68 {get ; set}
}
public class File5
{
public string Field_1 {get ; set}
public string Field_2 {get ; set}
.
.
.
public string Field_161 {get ; set}
}
}
The challenge is reading the data from the CSV into each of the dictionaries. Right now that is accomplished with 5 different functions (actually one function overloaded 5 times)
public Dictionary<string,File1>ReadFile(string file)
{
//Open and Parse File #1, and store in File1 class and accessed by file1 dictionary
}
public Dictionary<string,File2>ReadFile(string file)
{
//Open and Parse File #2, and store in File2 class and accessed by file2 dictionary
}
public Dictionary<string,File3>ReadFile(string file)
{
//Open and Parse File #3, and store in File3 class and accessed by file3 dictionary
}
public Dictionary<string,File4>ReadFile(string file)
{
//Open and Parse File #4, and store in File4 class and accessed by file4 dictionary
}
public Dictionary<string,File5>ReadFile(string file)
{
//Open and Parse File #5, and store in File5 class and accessed by file5 dictionary
}
The code for opening and parsing the CSV file is all virtually identical, differing only by the Type for the dictionary. So when I make a change to this function, I have to make sure I make identical changes to the other 4 functions, and I'm worried that it will make maintaining the code in the future more of a problem. Is there any possible way I can create a single function with no overloads where I can pass the type as a parameter to the function?
public Dictionary<string,[UnknownType]>ReadFile(string file, [UnknownType] typ)
{
//Open and Parse File and read into class specified by typ
}
Object Orientation my Friend. Coming from a VB6-perspective that might not be what you are use to. But instead of having File1 -> File5, why don't you have a "CVSFile"-object which you then, if you really must, derive from. Which would help you in Many ways.
Polymorphism
Check out MSDN on how to use Polymorphism in C#
Snippet from MSDN:
public class BaseClass
{
public void DoWork() { }
public int WorkField;
public int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public new void DoWork() { }
public new int WorkField;
public new int WorkProperty
{
get { return 0; }
}
}
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
Edit
Just to clearify a bit
When the new keyword is used, the new class members are called instead of the base class members that have been replaced. Those base class members are called hidden members. Hidden class members can still be called if an instance of the derived class is cast to an instance of the base class.
And if you like to use virtual methods instead:
In order for an instance of a derived class to completely take over a class member from a base class, the base class has to declare that member as virtual. This is accomplished by adding the virtual keyword before the return type of the member. A derived class then has the option of using the override keyword, instead of new, to replace the base class implementation with its own.
public class BaseClass
{
public virtual void DoWork() { }
public virtual int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public override void DoWork() { }
public override int WorkProperty
{
get { return 0; }
}
}
In this case the result will be this
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Also calls the new method.
All examples are take from MSDN
How to apply this on your example
Let's say that you have a LoadCSV-method, instead of having 5 different methods that returns each object, you can easily just say "Hey I will return a CVSFile, don't you care about anything else!".
There are a lot of good turorials out there, and the "Programming for kids" have the Best illustrations on the fundamentals. Check this out: Object Orientation For Kids ( No offense. )
There are some great answers about making a common FileBase
class that all of the file types descend from. You may be able to simplify things even further, though.
All of the file classes you have are identical except for the number of Field_X
values, so why not represent them all as a List<string>
? Since you're storing them with a key that is an integer, why not just use a List<T>
with its built-in indexing? Then your function would look sort of like this:
List<List<string>> Parse(string file)
{
List<List<string>> result = new List<List<string>>();
using (TextReader reader = File.OpenText(file))
{
string line = reader.ReadLine();
while (line != null)
{
result.Add(new List<string>(line.Split(',')));
line = reader.ReadLine();
}
}
return result;
}
And where you used to say
Dictionary<int, File1> file1 = Parse("file1.csv");
Console.Write(file1[0].Field_5);
You would now use
List<List<string>> file1 = Parse("file1.csv");
Console.Write(file1[0][5]);
If the field names are actually more interesting than Field_5
, have the function return a List<Dictionary<string,string>>
, and then you would use
List<List<string>> file1 = Parse("file1.csv");
Console.Write(file1[0]["SomeFieldName"]);
Why not just use object (the base for all .NET types) for your "unknown" type? You can then test/cast it as you need...
But, what's wrong with function overloads? Seems perfect for this scenario. Don't forget that you can have 5 public overloaded functions which can then delegate to private functions to achieve your goals in any way you wish.
You can do this, but you should not:
Dictionary<string, T> ReadFile<T>(string f) {...}
...
Dictionary<string, File1> d = ReadFile<File1>(filename);
The point of generics is to write code that is fully generic. The implementation of ReadFile will only be able to handle types File1 through File5, and therefore is not generic.
A better approach is as others have said: find an abstraction underlying all the file formats and create a class hierarchy which represents that abstraction.
But perhaps a better approach still is "do nothing". You have working code now. I understand wanting to do work now to avoid maintenance costs in the future, but you have to weigh those potential costs (that might never be realized) against the cost of building something highly general now that you might not need, or worse, might not actually do what you want in the future. I say stick with what you know works.
You must make that File_
classes to inherits a base class and return it:
public abstract classFileBase
{
// include common features here
}
public class File1 : FileBase
{
public string Field_1 {get ; set}
public string Field_2 {get ; set}
.
.
.
public string Field_10 {get ; set}
}
public Dictionary<string, FileBase>ReadFile(string file)
{
//Open and Parse File and read into class specified by typ
}
I was a little bored... here is an example using generics, extension methods, and LINQ... Welcome to .Net 3.5 :)
public interface IParser
{
object Parse(string input);
}
public interface IParser<T> : IParser
{
new T Parse(string input);
}
public class MyParser : IParser<MyObject>
{
#region IParser<MyObject> Members
public MyObject Parse(string input)
{
if (string.IsNullOrEmpty(input))
throw new ArgumentNullException("input");
if (input.Length < 3)
throw new ArgumentOutOfRangeException("input too short");
return new MyObject()
{
Field1 = input.Substring(0, 1),
Field2 = input.Substring(1, 1),
Field3 = input.Substring(2, 1)
};
}
object IParser.Parse(string input)
{
return this.Parse(input);
}
#endregion
}
public class MyObject
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
public static class ToolKit
{
public static Dictionary<string, TResult> ReadFile<TParser, TResult>(
this string fileName)
where TParser : IParser<TResult>, new()
where TResult : class
{
return fileName.AsLines()
.ReadFile<TParser, TResult>();
}
public static Dictionary<string, TResult> ReadFile<TParser, TResult>(
this IEnumerable<string> input)
where TParser : IParser<TResult>, new()
where TResult : class
{
var parser = new TParser();
var ret = input.ToDictionary(
line => line, //key
line => parser.Parse(line)); //value
return ret;
}
public static IEnumerable<string> AsLines(this string fileName)
{
using (var reader = new StreamReader(fileName))
while (!reader.EndOfStream)
yield return reader.ReadLine();
}
}
class Program
{
static void Main(string[] args)
{
var result = new[] { "123", "456", "789" }
.ReadFile<MyParser, MyObject>();
var otherResult = "filename.txt".ReadFile<MyParser, MyObject>();
}
}
Use virtual or abstract functions.
class Base {
virtual ReadFile();
}
class File1: Base {
override ReadFile(); //Reads File1
}
class File2: Base {
override ReadFile(); //Reads File2
}
....
and at the creation time , lets' say you have a list of the
List<Base> baseList
if(I need to read file File1)
baseList.Add(new File1());
And operate always on the virtual function of the Base class and never deal with implementation directly. Good luck.
Using generic methods:
public abstract class FileBase
{
public virtual void DoSomeParsing()
{
}
}
public class File1 : FileBase
{
}
public class Test
{
public Dictionary<string, T> ReadFile<T>(string file) where T : FileBase, new()
{
Dictionary<string, T> myDictionary = new Dictionary<string, T>();
myDictionary.Add(file, new T());
myDictionary[file].DoSomeParsing();
return myDictionary;
}
public object Testit()
{
Test test = new Test();
return test.ReadFile<File1>("C:\file.txt");
}
}
Of course, this doesn't really get around the inheritance problem and you are still going to have to cast as some base class or interface in the "as" clause above.
EDIT: Changed my code to make what I'm saying a little more clear.
精彩评论