HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Loading data into a number of different typed List<T>s: Can I do without reflection?

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
cannumberwithoutreflectionintodifferenttypedloadinglistdata

Problem

I'm not sure whether my distaste for reflection is justified or not, but it feels ucky that I'm using reflection here.

I'm trying to make a very simple class which has a number of List where T : Model with a load and save method.

The load method should:

  • For every list,



  • Open or create "Data/()/"



  • Sequentially load each file in the folder into memory by creating a new instance of T and calling Deserialize(Text)



The save method should:

  • For every list,



  • For every instance,



  • Call Serialize(), and dump the result into "/Data/()/(ID)"



Originally, I began writing code to loop through my Member list and my Game list and then realised I was going to be writing duplicate code for every list of data and switched to having a List> and looping that and ended up having to use reflection. I was wondering if anyone can think of a way of achieving my goals without using reflection or having duplicate code for every type of model stored.

I'll attach my current completed class below. I don't mind doing string modelClassName = models.GetType().GenericTypeArguments.First().Name; so much but it's the code:

Type modelType = models.GetType().GenericTypeArguments.First();

                    dynamic list = (this.GetType().GetRuntimeProperty(modelClassName + "s").GetValue(this));
                    dynamic loaded = Activator.CreateInstance(modelType);
                    loaded.Deserialize("");
                    list.Add(loaded);


That's really bothering me. It just feels wrong and suboptimal and I'm sure I'm not thinking this through properly.

Full code below:

```
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

using System.Linq;
using Chess_Club.Models;
using Windows.Storage;
using System.Diagnostics;

namespace Chess_Club.DAL {
class ChessClubContext {
public List> Models = new List>();
public List Games = new List();
public List Members = new List();

p

Solution

Reflection performance penalty would be negligible comparing to the serialization and IO cost, but you can surely work without reflection here.

In this code example, i'm demonstrating NET generics approach, which allows you to get specific type information statically (typeof(T)) and access members of a client class (ModelContext) with delegates, in place of reflection access to class members in your code.

You can take it a step further, replacing typeof(T).Name with an explicit collection folder name, this would require changing CreateRepository method signature slightly:

modelRepositories.Add(CreateRepository(() => Games, "Games"));


You can see the fix for async pattern usage. The correct signature is
async Task for a void method, so you could use await.

Usage of public properties with setters for collections is dangerous, even if it works in provided example, because delegate accesses the newest assigned collection each time it's called.

This code example is not for a Windows Store app, but the approach still applies.

public class ModelContext
{
    private readonly List modelRepositories = new List();
    public List Games = new List();
    public List Members = new List();

    public ModelContext()
    {
        modelRepositories.Add(CreateRepository(() => Games));
        modelRepositories.Add(CreateRepository(() => Members));
    }

    public async Task LoadData()
    {
        var rootFolderName = GetRootFolder();
        foreach (var modelRepository in modelRepositories)
        {
            await modelRepository.Load(rootFolderName);
        }
    }

    public async Task SaveData()
    {
        var rootFolderName = GetRootFolder();
        foreach (var modelRepository in modelRepositories)
        {
            await modelRepository.Save(rootFolderName);
        }
    }

    private static string GetRootFolder()
    {
        return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "TestRepository");
    }

    private static IModelRepository CreateRepository(Func> modelProperty) where T : Model, new()
    {
        return new ModelRepository(modelProperty);
    }
}

public interface IModelRepository
{
    Task Save(string rootFolderName);
    Task Load(string rootFolderName);
}

public class ModelRepository : IModelRepository where T: Model, new()
{
    private readonly Func> modelProperty;

    private readonly static string ModelName = typeof(T).Name;

    public ModelRepository(Func> modelProperty)
    {
        this.modelProperty = modelProperty;
    }

    public async Task Save(string rootFolderName)
    {
        var modelFolderName = GetFolderName(rootFolderName);
        if (!Directory.Exists(modelFolderName))
        {
            Directory.CreateDirectory(modelFolderName);
        }
        foreach (var model in modelProperty())
        {
            var modelFilePath = Path.Combine(modelFolderName, model.Id.ToString(CultureInfo.InvariantCulture));
            var model1 = model;
            await Task.Run(() => File.WriteAllText(modelFilePath, model1.Serialize()));
        }
    }

    public async Task Load(string rootFolderName)
    {
        var modelFolderName = GetFolderName(rootFolderName);
        foreach (var fileName in Directory.GetFiles(modelFolderName))
        {
            var fileName1 = fileName;
            var readAllText = await Task.Run(()=>File.ReadAllText(fileName1));
            LoadItem(readAllText);
        }
    }

    private static string GetFolderName(string rootFolderName)
    {
        return Path.Combine(rootFolderName, ModelName + "s");
    }

    protected void LoadItem(string s)
    {
        var loaded = new T();
        loaded.Deserialize(s);
        modelProperty().Add(loaded);
    }
}

Code Snippets

modelRepositories.Add(CreateRepository(() => Games, "Games"));
public class ModelContext
{
    private readonly List<IModelRepository> modelRepositories = new List<IModelRepository>();
    public List<Game> Games = new List<Game>();
    public List<Member> Members = new List<Member>();

    public ModelContext()
    {
        modelRepositories.Add(CreateRepository(() => Games));
        modelRepositories.Add(CreateRepository(() => Members));
    }

    public async Task LoadData()
    {
        var rootFolderName = GetRootFolder();
        foreach (var modelRepository in modelRepositories)
        {
            await modelRepository.Load(rootFolderName);
        }
    }

    public async Task SaveData()
    {
        var rootFolderName = GetRootFolder();
        foreach (var modelRepository in modelRepositories)
        {
            await modelRepository.Save(rootFolderName);
        }
    }

    private static string GetRootFolder()
    {
        return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "TestRepository");
    }

    private static IModelRepository CreateRepository<T>(Func<List<T>> modelProperty) where T : Model, new()
    {
        return new ModelRepository<T>(modelProperty);
    }
}

public interface IModelRepository
{
    Task Save(string rootFolderName);
    Task Load(string rootFolderName);
}

public class ModelRepository<T> : IModelRepository where T: Model, new()
{
    private readonly Func<List<T>> modelProperty;

    private readonly static string ModelName = typeof(T).Name;

    public ModelRepository(Func<List<T>> modelProperty)
    {
        this.modelProperty = modelProperty;
    }

    public async Task Save(string rootFolderName)
    {
        var modelFolderName = GetFolderName(rootFolderName);
        if (!Directory.Exists(modelFolderName))
        {
            Directory.CreateDirectory(modelFolderName);
        }
        foreach (var model in modelProperty())
        {
            var modelFilePath = Path.Combine(modelFolderName, model.Id.ToString(CultureInfo.InvariantCulture));
            var model1 = model;
            await Task.Run(() => File.WriteAllText(modelFilePath, model1.Serialize()));
        }
    }

    public async Task Load(string rootFolderName)
    {
        var modelFolderName = GetFolderName(rootFolderName);
        foreach (var fileName in Directory.GetFiles(modelFolderName))
        {
            var fileName1 = fileName;
            var readAllText = await Task.Run(()=>File.ReadAllText(fileName1));
            LoadItem(readAllText);
        }
    }

    private static string GetFolderName(string rootFolderName)
    {
        return Path.Combine(rootFolderName, ModelName + "s");
    }

    protected void LoadItem(string s)
    {
        var loaded = new T();
        loaded.Deserialize(s);
        modelProperty().Add(loaded);
    }
}

Context

StackExchange Code Review Q#62906, answer score: 4

Revisions (0)

No revisions yet.