简体   繁体   中英

C# generics code-bloat - should I be worried?

I'd like to ask something about generics.

I am trying to keep the code simple, and thus I will be making a single class to handle load/save for a game's savegame files. As each portion of the game has different requirements I'd like to keep this as easily accessible as possible:

public void Load<T>(string path, out T obj)
{
    BinaryFormatter bf = new BinaryFormatter();
    using (FileStream file = File.Open(Application.persistentDataPath + path, FileMode.Open))
    {
        obj = (T)bf.Deserialize(file);
    }
}

Now I can call this with a simple

TurnData x; s.Load("test.txt", out x);

The alternative would be to make the Load function return the object and then convert it to a TurnData type.

TurnData x = (TurnData)s.Load("test.txt");

I do not know much about C#. I assume that the code inside using(...) { ... } does not get executed if there is an error opening the file for example? If someone can confirm this that would be nice. The example code I have seen did not have any error handling, which seemed weird to me, so I added using?

So in this secondary version where the function returns the object instead of using an out parameter would need more complicated code for error checking and possible return null? It doesn't seem great.

So the real question is ... can I use the next version I have here or are there concerns that I should have due to the use of generics?

There is no generic code bloat for reference types - code is reused. With value types, though, CLR will generate a separate method for each type. See .NET Generics and Code Bloat .

The using statement has nothing to do with error handling. Using File.Open method you can expect to get the exceptions you will find here . You could avoid the abruptly stop of your program from any such an exception by wrapping your using statement in a try/cath construct like below:

public T Load<T>(string path)
{
    T obj = default(T);
    var bf = new BinaryFormatter();
    try
    {
        using (var file = File.Open(Application.persistentDataPath + path, FileMode.Open))
        {
            obj = (T)bf.Deserialize(file);
        }
    }
    catch(Exception exception)
    {
        // Log the exception
    }
    return obj;

}

Essentially you attempt to Open the file specified in the path. If that fails you just log the failure and your return null from the function.

Regarding the using statement, it provides

a convenient syntax that ensures the correct use of IDisposable objects.

as you can read more thoroughly here

As a side note regarding the signature of your method I would make a few comments. Consider the following method body and spot the differences with that we have above.

public T Load<T>(string path, IFormatter formatter)
{
    if(path ==null) throw new ArgumentNullException(nameof(path));
    if(formatter == null) throw new ArgumentNullException(nameof(formatter));

    T obj = default(T);
    try
    {
        using (var file = File.Open(path, FileMode.Open))
        {
            obj = (T)formatter.Deserialize(file);
        }
    }
    catch(Exception exception)
    {
        // Log the exception
    }
    return obj;   
}

and

var path = Path.Combine(Application.persistentDataPath, "test.txt");
var binaryFormatter = new BinaryFormatter();
var x = s.Load(path, binaryFormatter);

Making the above changes you make your method more easily to be tested through a unit test and more reliable since you make some precondition checking before the meat and potatoes of your method. What would had happened if you had passed a null path ? What would had happened if you had passed a null formatter ? etc...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM