1

In perl there's a rather simple method of tying a data structure to a file, whether it be a string, hash(dictionary in C#) or a simple array/list.

I've cobbled together my own half-assed solution in C# but I was wondering if there's a more inbuilt way to achieve this functionality?

Edit in response to comment below--The question, directly: Is there an inbuilt way to "tie" a class/dictionary to a file so any changes in either is reflected in the other? (Without doing as I've done below)

(Tieing a dictionary means that any changes in the dictionary are immediately reflected/updated in the file and declaring a tied var loads the object from disk if it already exists; see PerlTie )

My pseudo-tie'd class is below:

    #region options
    private float _Opacity;
    public float Opacity {
        get {
            return Opacity;
            }
        set {
            _Opacity = value;
            this.Save();
            }
        }

    private Font _Font;
    public Font Font {
        get {
            return _Font;
            }
        set {
            _Font = value;
            this.Save();
            }
        }

    private Color _FontColor;
    public Color FontColor {
        get {
            return _FontColor;
            }
        set {
            _FontColor = value;
            this.Save();
            }
        }

    private Color _BGColor;
    public Color BGColor {
        get {
            return _BGColor;
            }
        set {
            _BGColor = value;
            this.Save();
            }
        }

    private Point _Location;
    public Point Location {
        get {
            return _Location;
            }
        set {
            _Location = value;
            this.Save();
            }
        }

    private Size _Size;
    public Size Size {
        get {
            return _Size;
            }
        set {
            _Size = value;
            this.Save();
            }
        }

    private ushort _HistoryLines;
    public ushort HistoryLines {
        get {
            return _HistoryLines;
            }
        set {
            _HistoryLines = value;
            this.Save();
            }
        }

    private ChatType _ChatModes;
    public ChatType ChatModes {
        get {
            return _ChatModes;
            }
        set {
            _ChatModes = value;
            this.Save();
            }
        }

    private bool _Debugging;
    public bool Debugging {
        get {
            return _Debugging;
            }
        set {
            _Debugging = value;
            this.Save();
            }
        }
    #endregion options

    private FontConverter FontConvert;
    private FileInfo SettingsFile;

    private MLogConf() {
        }

    public MLogConf(string stateFile) {
        FontConvert = new FontConverter();
        try {

            if (!Directory.Exists(Path.GetDirectoryName(stateFile)))
                Directory.CreateDirectory(Path.GetDirectoryName(stateFile));
            if (!File.Exists(stateFile)) {
                FileStream fs = File.Create(stateFile);
                fs.Close();
                }
            SettingsFile = new FileInfo(stateFile);
            if (SettingsFile.Length == 0) {
                this.SetDefaultOptions();
                } else {
                if (!this.Load()) {
                    throw new FileLoadException("Couldn't load settings file");
                    }
                }
            } catch (Exception ex) {
            Trace.Write($"Failed to create MLogConf({nameof(stateFile)}) {ex.Message + Environment.NewLine + ex.StackTrace}");
            }
        }

    private bool Load() {
        if (SettingsFile == null)
            return false;
        try {
            byte[] data = File.ReadAllBytes(SettingsFile.FullName);
            using (MemoryStream m = new MemoryStream(data)) {
                using (BinaryReader reader = new BinaryReader(m)) {
                    _Opacity = reader.ReadSingle();
                    _Font = (Font)(FontConvert.ConvertFromString(Encoding.ASCII.GetString(Convert.FromBase64String(reader.ReadString()))));
                    _FontColor = Color.FromArgb(reader.ReadInt32());
                    _BGColor = Color.FromArgb(reader.ReadInt32());
                    _Location = new Point(reader.ReadInt32(), reader.ReadInt32());
                    _Size = new Size(reader.ReadInt32(), reader.ReadInt32());
                    _HistoryLines = reader.ReadUInt16();
                    _ChatModes = (ChatType)reader.ReadInt32();
                    _Debugging = reader.ReadBoolean();
                    }
                }
            } catch (Exception e) {
            Trace.WriteLine($"Exception reading binary data: {e.Message + Environment.NewLine + e.StackTrace}");
            return false;
            }
        return true;
        }

    private bool Save() {
        try {
            using (FileStream fs = new FileStream(SettingsFile.FullName, FileMode.Create)) {
                using (BinaryWriter writer = new BinaryWriter(fs)) {
                    writer.Write(_Opacity);
                    writer.Write(Convert.ToBase64String(Encoding.ASCII.GetBytes((string)FontConvert.ConvertTo(Font, typeof(string)))));
                    writer.Write(_FontColor.ToArgb());
                    writer.Write(_BGColor.ToArgb());
                    writer.Write(_Location.X);
                    writer.Write(_Location.Y);
                    writer.Write(_Size.Width);
                    writer.Write(_Size.Height);
                    writer.Write(_HistoryLines);
                    writer.Write((int)_ChatModes);
                    writer.Write(_Debugging);
                    }
                }
            } catch (Exception e) {
            Trace.WriteLine($"Exception writing binary data: {e.Message + Environment.NewLine + e.StackTrace}");
            return false;
            }
        return true;
        }


    private bool SetDefaultOptions() {
        this._BGColor = Color.Black;
        this._ChatModes = ChatType.Alliance | ChatType.Emote | ChatType.FreeCompany | ChatType.Linkshell | ChatType.Party | ChatType.SayShoutYell | ChatType.Tell;
        this._Opacity = 1f;
        this._Font = new Font("Verdana", 50);
        this._FontColor = Color.CornflowerBlue;
        this._Location = new Point(100, 400);
        this._Size = new Size(470, 150);
        this._HistoryLines = 512;
        this._Debugging = false;
        return this.Save();
        }
MisterNad
  • 432
  • 3
  • 14
  • So what's the question? Is the code relevant to this question? – user2864740 Jun 24 '17 at 05:46
  • My code is a long-winded example of what I'm after.. What I'm looking for, specifically, is a built-in .NET way of tying a structure to a file on disk. (Also, left the code up for anyone else that's looking for this functionality as it may prove useful to others.) – MisterNad Jun 24 '17 at 05:57
  • Tying a hash is to provide an object the interface of a hash. In C#, dictionaries are objects, so you'd simply create a class that implements `IDictionary`. – ikegami Jun 24 '17 at 09:11

1 Answers1

1

I suppose you are looking for an easy way to store class instances in files.

Serialization is the process of converting an object into a stream of bytes in order to store the object or transmit it to memory, a database, or a file. Its main purpose is to save the state of an object in order to be able to recreate it when needed. The reverse process is called deserialization.

The shortest solution I've found for C# here some time ago was Json.NET from Newtonsoft, available as NuGet package. It will do all the 'long class-properties-to-string code' for you and leave you with string ready to be written in file. Official site can provide code examples: http://www.newtonsoft.com/json

Save to file:

Product product = new Product();
product.Name = "Apple";
product.Expiry = new DateTime(2008, 12, 28);
product.Sizes = new string[] { "Small" };

string json = JsonConvert.SerializeObject(product);
// {
//   "Name": "Apple",
//   "Expiry": "2008-12-28T00:00:00",
//   "Sizes": [
//     "Small"
//   ]
// }

Read from file:

string json = @"{
  'Name': 'Bad Boys',
  'ReleaseDate': '1995-4-7T00:00:00',
  'Genres': [
    'Action',
    'Comedy'
  ]
}";

Movie m = JsonConvert.DeserializeObject<Movie>(json);

string name = m.Name;
// Bad Boys
  • I'm aware of serialization and was attempting to avoid it.. It's not much different than what I'm already doing, which is what I'm not wanting. I'm trying to get a live-object that exists on disk and any access is essentially redirected to the stored-on-disk object. Without requiring me to call Object.Serialize(file, etc) every time the object is updated.. So in short, I'm guessing there's no native (eg; no external libs) way to accomplish this? – MisterNad Jun 24 '17 at 08:29
  • Sounds similar to memory-mapped file. Try this: [another stackoverflow answer](https://stackoverflow.com/a/1859843/3105154) – Andrew Myhalchuk Jun 24 '17 at 08:34
  • I hadn't considered using an MMF, though in my own limited experience, I'm not sure it's going to produce quite the same functionality I'm trying to achieve, but I'll give it a go nonetheless. Could you update your answer with an example if you've got the time? – MisterNad Jun 24 '17 at 08:39
  • @MisterNad [MSDN](https://blogs.msdn.microsoft.com/salvapatuel/2009/06/08/working-with-memory-mapped-files-in-net-4/) can give you more code examples than I. I was just reading about MMF while trying to build reliable memory exchange from C++ app to C# app. I ended up with a solution using inter-process memory reading, without using files. – Andrew Myhalchuk Jun 24 '17 at 08:48
  • @MisterNad In case you are not required to change disk storage very often - I guess wrinting a class that sync your live-object with a file if (it was changed and once in a while, say once a second) will be less time-consuming. – Andrew Myhalchuk Jun 24 '17 at 08:53
  • This is a permanent-storage for a window's placement and various options related to it, so it will not be updated often.. It's a save-window-state so the user doesn't have to reconfigure the window/chatlog options every time they start the app. (It's an autotranslation tool for an MMO, JP->EN translation.. It sits on top of the game (and hides the taskbar) with configurable opacity, click-through and has sys-wide hotkeys) This is why I'd like the options to be "live", they're changed on the fly so that if the program terminates unexpectedly, options are still in tact for the next run. – MisterNad Jun 24 '17 at 09:59
  • Being that you haven't posted another answer in-line with what I was looking for, I'll just accept this answer and continue using the method I posted in the OP.. Thanks for your replies Andrew :) – MisterNad Jun 24 '17 at 12:03