I need to write an Exception class that the message is determined automatically according to the input parameters. How can I pass that dynamic message to the base()?
Here is the codes of what I want to do but it's not working
public class MyException : Exception
{
public MyException(int ErrCode, Dictionary<string, string> Params) : base(msg_external)
{
ErrorDetail = ErrorList[ErrCode];
string msg_internal = "";
string msg_external = "";
foreach (var item in Params)
{
msg_internal = ErrorDetail.MessageInternal.Replace(item.Key, item.Value);
msg_external = ErrorDetail.MessageExternal.Replace(item.Key, item.Value);
}
//now I need to pass msg_external to the base(). how?
}
}
This question is not about calling constructor after my constructor, it is about passing a different value to the parent constructor.
Here's code that'll do what you want.
Assumes good input. Error checking recommended.
public class MyException : Exception
{
private static string BaseExceptionMessage(int ErrCode, Dictionary<string,string> Params)
{
return Params.Aggregate(ErrorList[ErrCode].MessageExternal, (i, a) => i.Replace(a.Key, a.Value));
}
public MyException(int ErrCode, Dictionary<string, string> Params) : base(BaseExceptionMessage(ErrCode, Params))
{
ErrorDetail = ErrorList[ErrCode];
string msg_internal = "";
string msg_external = "";
foreach (var item in Params)
{
msg_internal = ErrorDetail.MessageInternal.Replace(item.Key, item.Value);
msg_external = ErrorDetail.MessageExternal.Replace(item.Key, item.Value);
}
}
}
Related
I am trying to make a source generator for mapping columns from the google bigquery api client to class properties. I'm having trouble getting custom column names from a ColumnAttribute on the properties. ConstructorArguments is always empty and columnAttribute.AttributeClass in this sample is always an ErrorTypeSymbol. If I try to load that type using compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.Schema.ColumnAttribute") the result is always null.
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace BigQueryMapping;
[Generator]
public class BigQueryMapperGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// add marker attribute
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource("BigQueryMappedAttribute.g.cs", SourceText.From(Attribute, Encoding.UTF8)));
// add static interface
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource("BigQueryMappedInterface.g.cs", SourceText.From(Interface, Encoding.UTF8)));
// get classes
IncrementalValuesProvider<ClassDeclarationSyntax> classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (s, _) => s is ClassDeclarationSyntax c && c.AttributeLists.Any(),
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx)
)
.Where(static m => m is not null)!;
IncrementalValueProvider<(Compilation Compilation, ImmutableArray<ClassDeclarationSyntax>Syntaxes)>
compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
context.RegisterSourceOutput(compilationAndClasses,
static (spc, source) => Execute(source.Compilation, source.Syntaxes, spc));
static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
{
var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists)
{
foreach (var attributeSyntax in attributeListSyntax.Attributes)
{
var fullName = context.SemanticModel.GetTypeInfo(attributeSyntax).Type?.ToDisplayString();
if (fullName == "BigQueryMapping.BigQueryMappedAttribute")
return classDeclarationSyntax;
}
}
return null;
}
static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes,
SourceProductionContext context)
{
try
{
if (classes.IsDefaultOrEmpty)
return;
var distinctClasses = classes.Distinct();
var classesToGenerate = GetTypesToGenerate(compilation, distinctClasses, context.CancellationToken);
foreach (var classToGenerate in classesToGenerate)
{
var result = GeneratePartialClass(classToGenerate);
context.AddSource($"{classToGenerate.RowClass.Name}.g.cs", SourceText.From(result, Encoding.UTF8));
}
}
catch (Exception e)
{
var descriptor = new DiagnosticDescriptor(id: "BQD001",
title: "Error creating bigquery mapper",
messageFormat: "{0} {1}",
category: "BigQueryMapperGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
context.ReportDiagnostic(Diagnostic.Create(descriptor, null, e.Message, e.StackTrace));
}
}
}
static IEnumerable<ClassToGenerate> GetTypesToGenerate(Compilation compilation,
IEnumerable<ClassDeclarationSyntax> classes,
CancellationToken ct)
{
var columnAttributeSymbol =
compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.Schema.ColumnAttribute");
foreach (var #class in classes)
{
Debug.WriteLine($"Checking class {#class}");
ct.ThrowIfCancellationRequested();
var semanticModel = compilation.GetSemanticModel(#class.SyntaxTree);
if (semanticModel.GetDeclaredSymbol(#class) is not INamedTypeSymbol classSymbol)
continue;
var info = new ClassToGenerate(classSymbol, new());
foreach (var member in classSymbol.GetMembers())
{
if (member is IPropertySymbol propertySymbol)
{
if (propertySymbol.DeclaredAccessibility == Accessibility.Public)
{
if (propertySymbol.SetMethod is not null)
{
var columnName = propertySymbol.Name;
var columnAttribute = propertySymbol.GetAttributes().FirstOrDefault(a =>
a.AttributeClass!.ToDisplayString() == "Column");
if (columnAttribute is not null)
{
if (!columnAttribute.ConstructorArguments.IsDefaultOrEmpty)
{
var nameArg = columnAttribute.ConstructorArguments.First();
if (nameArg.Value is string name)
{
columnName = name;
}
}
}
info.Properties.Add((columnName, propertySymbol));
}
}
}
}
yield return info;
}
}
static string GeneratePartialClass(ClassToGenerate c)
{
var sb = new StringBuilder();
sb.Append($#"// <auto-generated/>
namespace {c.RowClass.ContainingNamespace.ToDisplayString()}
{{
public partial class {c.RowClass.Name} : BigQueryMapping.IBigQueryGenerated<{c.RowClass.Name}>
{{
public static {c.RowClass.Name} FromBigQueryRow(Google.Cloud.BigQuery.V2.BigQueryRow row)
{{
return new {c.RowClass.Name}
{{");
foreach (var (columnName, property) in c.Properties)
{
// would like to check if key exists but don't see any sort of ContainsKey implemented on BigQueryRow
var tempName = $"___{property.Name}";
var basePropertyType = property.Type.WithNullableAnnotation(NullableAnnotation.None).ToDisplayString();
if (basePropertyType.EndsWith("?"))
{
basePropertyType = basePropertyType.Substring(default, basePropertyType.Length - 1);
}
sb.Append($#"
{property.Name} = row[""{columnName}""] is {basePropertyType} {tempName} ? {tempName} : default,");
}
sb.Append($#"
}};
}}
}}
}}");
return sb.ToString();
}
private record struct ClassToGenerate(INamedTypeSymbol RowClass,
List<(string ColumnName, IPropertySymbol Property)> Properties);
public const string Attribute = /* lang=csharp */ #"// <auto-generated/>
namespace BigQueryMapping {
[System.AttributeUsage(System.AttributeTargets.Class)]
public class BigQueryMappedAttribute : System.Attribute
{
}
}";
public const string Interface = /* lang=csharp */ #"// <auto-generated/>
namespace BigQueryMapping {
public interface IBigQueryGenerated<TRow> {
static TRow FromBigQueryRow(Google.Cloud.BigQuery.V2.BigQueryRow row) => throw new System.NotImplementedException();
}
}";
}
I have tried this with both System.ComponentModel.DataAnnotations.Schema.ColumnAttribute and a custom attribute injected via context.RegisterPostInitializationOutput to similar results. I have also tried rewriting this to use ISourceGenerator instead of IIncrementalGenerator and gotten the same behavior. Am wondering what I need to do to get columnAttribute loading correctly.
Thanks for any help in advance
It's hard to psychic debug the code but this does jump out:
var columnAttribute = propertySymbol.GetAttributes().FirstOrDefault(a =>
a.AttributeClass!.ToDisplayString() == "Column");
I'd guess the class here would be the fully qualified name. You already fetched the ColumnAttribute type earlier, so what'd be even better is to compare the AttributeClass to that type rather than doing string checks like this.
As a semi-related comment, if you're looking for types/members that are annotated with a specific attribute, rather than doing it yourself we have SyntaxValueProvider.ForAttributeWithMetadataName which is pretty heavily optimized to reduce the performance impact on your Visual Studio. It requires 17.3 or higher, but as long as you're OK with that it'll generally help performance.
I'm trying to parse an XML file of size around 400KB. But I cannot overcome the stack overflow exception. First I create XmlReader and pass it to the XML file. Then I create XElement from the XmlReader.
This is my code:
private ViewContent ParseToView(XElement xElement)
{
ViewContent viewContent = new ViewContent();
viewContent.elementName = xElement.Name.LocalName;
foreach (XAttribute item in xElement.Attributes())
{
viewContent.attributes.Add(new ElementAttribute(item.Name.ToString(), item.Value));
}
foreach (XElement item in xElement.Elements())
{
viewContent.viewContents.Add(ParseToView(xElement));
}
return new ViewContent();
}
}
public class ViewContent
{
public string elementName;
public List<ElementAttribute> attributes = new List<ElementAttribute>();
public List<ViewContent> viewContents = new List<ViewContent>();
}
public class ElementAttribute
{
public ElementAttribute(string attributeName, string attributeValue)
{
this.attributeName = attributeName;
this.attributeValue = attributeValue;
}
public string attributeName;
public string attributeValue;
}
In method ParseToView you are calling same method recursively but you're calling it with same parameter - viewContent.viewContents.Add(ParseToView(xElement)); - this causes stackoverflow:
viewContent.viewContents.Add(ParseToView(xElement));
should have probably been:
viewContent.viewContents.Add(ParseToView(item));
in method:
private ViewContent ParseToView(XElement xElement)
{
ViewContent viewContent = new ViewContent();
viewContent.elementName = xElement.Name.LocalName;
foreach (XAttribute item in xElement.Attributes())
{
viewContent.attributes.Add(new ElementAttribute(item.Name.ToString(), item.Value));
}
foreach (XElement item in xElement.Elements())
{
viewContent.viewContents.Add(ParseToView(xElement)); // <-Faulty line here
}
return new ViewContent();
}
}
I have a failing testcase that depends on an external module.
I want to use Rhino Mock to generate a report on called functions.
I created a minimal example that illustrates my problem:
using NUnit.Framework;
using Rhino.Mocks;
using System;
namespace StackOverflow_namespace
{
public interface IUsefulService
{
object HiddenAmongManyCalls();
}
public class ThirdPartyBase
{
private int a = 42;
public ThirdPartyBase(IUsefulService service)
{
object liveFastDieYoung = service.HiddenAmongManyCalls();
liveFastDieYoung.Equals(a);
}
}
public class MyParty : ThirdPartyBase
{
public MyParty(IUsefulService service) : base(service)
{
}
}
[TestFixture]
class StackOverflow
{
[Test]
public void Hypothetical()
{
IUsefulService service = MockRepository.GenerateMock<IUsefulService>();
try
{
var party = new MyParty(service);
}
catch(Exception e)
{
string[] calls = MagicallyGetTheCallsThatWereMadeToTheMock();
foreach(var call in calls)
{
//with my visual studio testrunner for nunit 3 I can investigate stored console output
Console.WriteLine(call);
}
Assert.Fail("Excpexted no exception but was '" + e.GetType().Name + "': " + e.Message);
}
}
private string[] MagicallyGetTheCallsThatWereMadeToTheMock()
{
return new[]
{
"This is where I am lost, I do not know how to get the calls from the repository."
};
}
}
}
I tried to find something online without success.
Do Rhino Mocks record all calls and can I access that list?
Edit:
An attempt to verify Expectations did not work since I am looking for calls I did not expect.
I could build a list of calls using GetArgumentsForCallsMadeOn. I can reflect on the Interface. I started on a method for that but I currently fail to see how I can convert a MethodInfo to an Action<T>.
private IEnumerable<string> GetCallsList<Interface>(Interface rhinomock)
{
Type interfaceType = typeof(Interface);
List<MethodInfo> interfaceMethodInfos = new List<MethodInfo>();
List<string> returnInfos = new List<string>();
StringBuilder callbuilder = new StringBuilder();
foreach (var property in interfaceType.GetProperties())
{
interfaceMethodInfos.Add(property.GetGetMethod());
interfaceMethodInfos.Add(property.GetSetMethod());
}
foreach (var method in interfaceType.GetMethods())
{
interfaceMethodInfos.Add(method);
}
foreach (var methodinfo in interfaceMethodInfos)
{
Action<Interface> magic = null; //convert methodinfo into action - still missing
var calls = rhinomock.GetArgumentsForCallsMadeOn(magic); //magic is currently null, here be crash
foreach (var call in calls)
{
bool more = false;
callbuilder.Clear().Append(interfaceType.Name).Append('.').Append(methodinfo.Name).Append('(');
foreach (var parameter in call)
{
if (more){ callbuilder.Append(", "); }
if (null == parameter) { callbuilder.Append("<null>"); }
else { callbuilder.Append(parameter.ToString()); }
more = true;
}
callbuilder.Append(')');
string callInfo = callbuilder.ToString();
returnInfos.Add(callInfo);
}
}
return returnInfos;
}
I was able to use reflection to get the output I wanted.
Here is the minimal example where the test fails and the output contains all method calls.
using NUnit.Framework;
using Rhino.Mocks;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace StackOverflow_namespace
{
public interface IUsefulService
{
object HiddenAmongManyCalls();
string TestCall2(string arg1, int arg2);
string FULLACCESS { get; set; }
string READONLY { get; }
}
public class ThirdPartyBase
{
private int a = 42;
public ThirdPartyBase(IUsefulService service)
{
service.TestCall2("callA", 1);
service.TestCall2("callB", 1);
object liveFastDieYoung = service.HiddenAmongManyCalls();
service.TestCall2("callA", 2);
service.TestCall2("callB", 2);
var a = service.FULLACCESS;
var b = service.READONLY;
service.FULLACCESS = "some";
liveFastDieYoung.Equals(a);
}
}
public class MyParty : ThirdPartyBase
{
public MyParty(IUsefulService service) : base(service)
{
}
}
[TestFixture]
class StackOverflow
{
[Test]
public void Hypothetical()
{
IUsefulService service = MockRepository.GenerateMock<IUsefulService>();
try
{
var party = new MyParty(service);
}
catch (Exception e)
{
var calls = GetCallsList(service);
foreach (var call in calls)
{
//with my visual studio testrunner for nunit 3 I can investigate stored console output
Console.WriteLine(call);
}
Assert.Fail("Excpexted no exception but was '" + e.GetType().Name + "': " + e.Message);
}
}
private IEnumerable<string> GetCallsList<Interface>(Interface rhinomock)
{
Type interfaceType = typeof(Interface);
List<MethodInfo> interfaceMethodInfos = new List<MethodInfo>();
List<string> returnInfos = new List<string>();
StringBuilder callbuilder = new StringBuilder();
foreach (var property in interfaceType.GetProperties())
{
AddMethodInfoIfValid(interfaceMethodInfos, property.GetGetMethod());
AddMethodInfoIfValid(interfaceMethodInfos, property.GetSetMethod());
}
foreach (var method in interfaceType.GetMethods())
{
AddMethodInfoIfValid(interfaceMethodInfos, method);
}
foreach (var methodinfo in interfaceMethodInfos)
{
int paramcount = methodinfo.GetParameters().Length;
object[] args = new object[paramcount];
Action<Interface> lambdacall = (i) => methodinfo.Invoke(i, args);
var calls = rhinomock.GetArgumentsForCallsMadeOn(lambdacall);
foreach (var call in calls)
{
bool more = false;
callbuilder.Clear().Append(interfaceType.Name).Append('.').Append(methodinfo.Name).Append('(');
foreach (var parameter in call)
{
if (more) { callbuilder.Append(", "); }
if (null == parameter) { callbuilder.Append("<null>"); }
else {
callbuilder
.Append('(').Append(parameter.GetType().Name).Append(")'")
.Append(parameter.ToString()).Append("'");
}
more = true;
}
callbuilder.Append(')');
string callInfo = callbuilder.ToString();
returnInfos.Add(callInfo);
}
}
return returnInfos;
}
private static void AddMethodInfoIfValid(List<MethodInfo> interfaceMethodInfos, MethodInfo methodinfo)
{
if (null != methodinfo)
{
interfaceMethodInfos.Add(methodinfo);
}
}
}
}
Does anyone know of a C# equivalent of this occassional PHP task:
Convert dot syntax like "this.that.other" to multi-dimensional array in PHP
That is, to convert a list of strings like level1.level2.level3 = item into a dictionary or multidimensional array?
I'd assume the dictionary would need to hold items of type object and I'd later cast them to Dictionary<string, string> or a string if it's a final item.
Does code like this work?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string input = "level1.level2.level3 = item";
string pattern = "^(?'keys'[^=]+)=(?'value'.*)";
Match match = Regex.Match(input, pattern);
string value = match.Groups["value"].Value.Trim();
string[] keys = match.Groups["keys"].Value.Trim().Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries);
Dictionary<string, List<string>> dict = new Dictionary<string, List<string>>();
foreach (string key in keys)
{
if (dict.ContainsKey("key"))
{
dict[key].Add(value);
}
else
{
dict.Add(key, new List<string>() { value });
}
}
}
}
}
I guess you could do it like this, though (i'm sure more optimization could be done)
using System;
using System.Collections.Generic;
public class Program
{
public class IndexedTree {
private readonly IDictionary<string, IndexedTree> _me;
private object _value;
private readonly string _splitKey = ".";
public IndexedTree this[string key] {
get {
return _me[key];
}
}
public object Value { get; set; }
public void Add(string dottedItem) {
if ( string.IsNullOrWhiteSpace( dottedItem ) ) {
throw new ArgumentException("dottedItem cannot be empty");
}
int index;
if ( (index = dottedItem.IndexOf( _splitKey ) ) < 0 ) {
throw new ArgumentException("dottedItem didn't contain " + _splitKey);
}
string key = dottedItem.Substring(0, index), rest = dottedItem.Substring(index + 1);
IndexedTree child;
if (_me.ContainsKey(key)) {
child = _me[key];
} else {
child = new IndexedTree( _splitKey );
_me.Add(key, child);
}
if (rest.IndexOf(_splitKey) >= 0) {
child.Add(rest);
} else {
// maybe it can be checked if there is already a value set here or not
// in case there is a warning or error might be more appropriate
child.Value = rest;
}
}
public IndexedTree(string splitKey) {
_splitKey = splitKey;
_me = new Dictionary<string, IndexedTree>();
}
}
public static void Main()
{
IndexedTree tree = new IndexedTree(".");
tree.Add("Level1.Level2.Level3.Item");
tree.Add("Level1.Level2.Value");
Console.WriteLine(tree["Level1"]["Level2"].Value);
Console.WriteLine(tree["Level1"]["Level2"]["Level3"].Value);
}
}
You could see the result here:
https://dotnetfiddle.net/EGagoz
I am currently developing a software that will be used by users that should not be able to access the back-end of it all but should still be able to easily change configuration/settings for the application.
I decided the best approach would be a custom "configuration file (.cfg)" located in the root of the final build.
Simple example of the .cfg file:
serveraddress='10.10.10.10'
serverport='1234'
servertimeout='15000'
Since I wanted the configuration file to easily be extended I decided to use some custom attributes and some simple LINQ.
This does work like I expect it to, but since I am still a novice in .net I am afraid I have not gone with the best approach and my question is therefor:
Is there anything I can do to improve this?
Or is there just generally a better approach for this?
This is my code for reading the configuration file and assigning the values to it's corresponding properties.
ConfigFileHandler.cs
public void ReadConfigFile()
{
var cfgFile = new ConfigFile();
var configLines = File.ReadAllLines("configfile.cfg");
var testList = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToList();
foreach (var prop in typeof(ConfigFile).GetProperties())
{
var attrs = (ConfigFileFieldAttribute[])prop.GetCustomAttributes
(typeof(ConfigFileFieldAttribute), false);
foreach (var t in from attr in attrs from t in testList where t.Item1 == attr.Name select t)
{
prop.SetValue(cfgFile, t.Item2);
}
}
}
ConfigFile.cs
class ConfigFile
{
private static string _serverAddress;
private static int _serverPort;
private static int _serverTimeout;
[ConfigFileField(#"serveraddress")]
public string ServerAddress
{
get { return _serverAddress; }
set { _serverAddress= value; }
}
[ConfigFileField(#"serverport")]
public string ServerPort
{
get { return _serverPort.ToString(); }
set { _serverPort= int.Parse(value); }
}
[ConfigFileField(#"servertimeout")]
public string ServerTimeout
{
get { return _serverTimeout.ToString(); }
set { _serverTimeout= int.Parse(value); }
}
}
any tips on writing better looking code would be highly appreciated!
UPDATE:
Thanks for all the feedback.
Below is the final classes!
https://dotnetfiddle.net/bPMnJA for a live example
Please note, this is C# 6.0
ConfigFileHandler.cs
public class ConfigFileHandler
{
public void ReadConfigFile()
{
var configLines = File.ReadAllLines("configfile.cfg");
var configDictionary = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2);
ConfigFile.SetDictionary(configDictionary);
}
}
ConfigFile.cs
public class ConfigFile
{
private static Dictionary<string, string> _configDictionary;
public string ServerAddress => PullValueFromConfig<string>("serveraddress", "10.1.1.10");
public int ServerPort => PullValueFromConfig<int>("serverport", "3306");
public long ServerTimeout => PullValueFromConfig<long>("servertimeout", "");
private static T PullValueFromConfig<T>(string key, string defaultValue)
{
string value;
if (_configDictionary.TryGetValue(key, out value) && value.Length > 0)
return (T) Convert.ChangeType(value, typeof (T));
return (T) Convert.ChangeType(defaultValue, typeof (T));
}
public static void SetDictionary(Dictionary<string, string> configValues)
{
_configDictionary = configValues;
}
}
You could keep the simplicity of your config file and get rid of the nested loops by loading the values into a dictionary and then passing that into your ConfigFile class.
public static void ReadConfigFile()
{
var configLines = File.ReadAllLines("configfile.cfg");
var testList = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2);
var cfgFile = new ConfigFile(testList);
}
The new ConfigFile class:
class ConfigFile
{
private Dictionary<string, string> _configDictionary;
public ConfigFile(Dictionary<string, string> configValues)
{
_configDictionary = configValues;
}
public string ServerAddress
{
get { return PullValueFromConfig("serveraddress", "192.168.1.1"); }
}
public string ServerPort
{
get { return PullValueFromConfig("serverport", "80"); }
}
public string ServerTimeout
{
get { return PullValueFromConfig("servertimeout", "900"); }
}
private string PullValueFromConfig(string key, string defaultValue)
{
string value;
if (_configDictionary.TryGetValue(key, out value))
return value;
return defaultValue;
}
}
I decided to use a custom "configuration file (.cfg)" located in the root of the final build.
Good idea. For cleaner code, you could use JSON and JSON.NET for de/serialization and put the read/write into the ConfigFile class. Here is an example that is live as a fiddle.
The ConfigFile class is responsible for loading and saving itself and uses JSON.NET for de/serialization.
public class ConfigFile
{
private readonly static string path = "somePath.json";
public string ServerAddress { get; set; }
public string ServerPort { get; set; }
public string ServerTimeout { get; set; }
public void Save()
{
var json = JsonConvert.SerializeObject(this, Formatting.Indented);
File.WriteAllText(path, json)
}
public static ConfigFile Load()
{
var json = File.ReadAllText(path);
return JsonConvert.DeserializeObject<ConfigFile>(json);
}
}
Here is how you would use it to load the file, change its properties, and save.
ConfigFile f = ConfigFile.Load();
f.ServerAddress = "0.0.0.0";
f.ServerPort = "8080";
f.ServerTimeout = "400";
f.Save();
We use the .json file extension as a convention. You could still use .cfg because it's just plain text with a specific syntax. The resultant config file content from the above usage is this:
{
"ServerAddress":"0.0.0.0",
"ServerPort":"8080",
"ServerTimeout":"400"
}
You could just tell your clients to "change the numbers only". Your approach is fine, as far as I'm concerned. The above is just a cleaner implementation.
Firstly, I would do what Phil did, and store your testlist in a Dictionary.
var configLines = File.ReadAllLines("configfile.cfg");
var testDict = configLines.Select(line => line.Split('=', 2))
.ToDictionary(s => s[0], s => s[1].Replace("'", ""));
Then you can clean up the property assignment LINQ a bit:
foreach (var prop in typeof(ConfigFile).GetProperties())
{
var attr = prop.GetCustomAttributes(false)
.OfType<ConfigFileFieldAttribute>()
.FirstOrDefault();
string val;
if (attr != null && testDict.TryGetValue(attr.Name, out val))
prop.SetValue(cfgFile, val);
}
You might even be able to call:
var attr = prop.GetCustomAttributes<ConfigFileFieldAttribute>(false).FirstOrDefault();
Don't have an IDE on me so I can't check right now