public static class Settings
{
private static System.Threading.Timer Timer = new System.Threading.Timer(OnTimerExpire, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
private static System.Collections.Generic.Dictionary<string, object> dic;
private static string LastCause = null;
private static string LockString = "---- SETTING LOCK ----";
public static bool IsNewFile { get; private set; } = false;
public static Version PrevVersion { get; private set; } = new Version(0, 0, 0);
public enum GraphicMode
{
AUTO = 0,
GDI = 1,
DIB = 2,
FBO = 3,
}
public static GraphicMode ForcedGraphicMode { get; set; } = GraphicMode.AUTO; // forced by command line
public static GraphicMode ConfiguredGraphicMode // stored in settings
{
get { return (GraphicMode)GetObject("ConfiguredGraphicMode", 0); }
set { SetObject("ConfiguredGraphicMode", (int)value); }
}
public static GraphicMode RequestedGraphicMode => ForcedGraphicMode != GraphicMode.AUTO ? ForcedGraphicMode : ConfiguredGraphicMode ;
public static GraphicMode CurrentGraphicMode { get; set; } = GraphicMode.AUTO; // actually in use
static string filename
{
get
{
string basename = "YourProgram.Settings.bin";
string fullname = System.IO.Path.Combine(Core.DataPath, basename);
if (!System.IO.File.Exists(fullname) && System.IO.File.Exists(basename))
System.IO.File.Copy(basename, fullname);
return fullname;
}
}
static Settings()
{
try
{
IsNewFile = !System.IO.File.Exists(filename);
if (!IsNewFile)
{
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter f = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
f.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
using (System.IO.FileStream fs = new System.IO.FileStream(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.None))
{
dic = (System.Collections.Generic.Dictionary<string, object>)f.Deserialize(fs);
fs.Close();
}
}
}
catch { }
if (dic == null)
dic = new System.Collections.Generic.Dictionary<string, object>();
PrevVersion = GetObject("Current Program Version", new Version(0, 0, 0));
SetObject("Current Program Version", Program.CurrentVersion);
}
public static T GetObject<T>(string key, T defval)
{
try
{
if (ExistObject(key))
{
object obj = dic[key];
if (obj != null && obj.GetType() == typeof(T))
return (T)obj;
}
}
catch
{
}
return defval;
}
public static object GetAndDeleteObject(string key, object defval)
{
object rv = ExistObject(key) && dic[key] != null ? dic[key] : defval;
DeleteObject(key);
return rv;
}
public static void SetObject(string key, object value, bool forcesave = false) //use force-save if calling with a complex object that not support Equal comparison
{
if (ExistObject(key))
{
bool isdifferent = !Equals(dic[key], value);
if (value is object[] && dic[key] is object[])
isdifferent = !ArraysEqual((object[])dic[key], (object[])value);
dic[key] = value;
if (isdifferent || forcesave)
TriggerSave(key);
}
else
{
dic.Add(key, value);
TriggerSave(key);
}
}
private static bool ArraysEqual<T>(T[] a1, T[] a2)
{
if (ReferenceEquals(a1, a2))
return true;
if (a1 == null || a2 == null)
return false;
if (a1.Length != a2.Length)
return false;
var comparer = System.Collections.Generic.EqualityComparer<T>.Default;
for (int i = 0; i < a1.Length; i++)
{
if (!comparer.Equals(a1[i], a2[i])) return false;
}
return true;
}
private static void TriggerSave(string cause)
{
lock (LockString)
{
LastCause = cause;
Timer.Change(2000, System.Threading.Timeout.Infinite);
}
}
internal static void Exiting()
{
OnTimerExpire(null);
}
private static void OnTimerExpire(object state)
{
lock (LockString)
{
Timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
InternalSaveFile();
}
}
private static void InternalSaveFile()
{
if (LastCause != null)
{
System.Diagnostics.Debug.WriteLine($"Save setting file [{LastCause}]");
try
{
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter f = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
f.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
using (System.IO.FileStream fs = new System.IO.FileStream(filename, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
{
f.Serialize(fs, dic);
fs.Close();
}
}
catch { }
}
LastCause = null;
}
internal static void DeleteObject(string key)
{
if (ExistObject(key))
{
dic.Remove(key);
TriggerSave(key);
}
}
internal static bool ExistObject(string key)
{
return dic.ContainsKey(key);
}
}
这个C#代码定义了一个静态类Settings
,用于管理应用程序的设置,包括从二进制文件中加载、保存和检索设置。以下是代码的主要功能和分析:
主要功能
-
设置存储:
-
设置存储在一个二进制文件(
YourProgram.Settings.bin
)中,文件路径为Core.DataPath
。 -
如果文件不存在,则会创建一个新文件。如果当前目录中存在同名文件,则会将其复制到目标路径。
-
-
图形模式管理:
-
类中定义了一个枚举
GraphicMode
,包含AUTO
、GDI
、DIB
和FBO
等值。 -
提供了多个属性来管理图形模式:
-
ForcedGraphicMode
:通过命令行强制设置的图形模式。 -
ConfiguredGraphicMode
:存储在设置中的图形模式。 -
RequestedGraphicMode
:确定实际使用的图形模式(如果ForcedGraphicMode
不是AUTO
,则优先使用它)。 -
CurrentGraphicMode
:当前实际使用的图形模式。
-
-
-
延迟保存:
-
当设置被修改时,会触发一个延迟保存操作(2秒延迟),以避免频繁的文件写入。
-
使用
System.Threading.Timer
实现异步保存。
-
-
线程安全:
-
使用锁(
LockString
)确保对设置字典和文件操作的线程安全访问。
-
-
序列化:
-
使用
BinaryFormatter
对设置进行序列化和反序列化。 -
BinaryFormatter
配置为FormatterAssemblyStyle.Simple
以提高兼容性。
-
-
实用方法:
-
GetObject<T>
:根据键获取设置值,如果键不存在或发生错误,则返回默认值。 -
SetObject
:设置一个值,并在值发生变化时触发保存。 -
DeleteObject
:删除一个设置并触发保存。 -
ExistObject
:检查设置是否存在。 -
GetAndDeleteObject
:获取设置值并删除它。
-
-
数组比较:
-
ArraysEqual
方法用于比较两个数组是否相等,确保基于数组的设置能够正确处理。
-
-
版本管理:
-
使用
Version
类跟踪程序的当前版本和上一个版本。
-
-
错误处理:
-
文件操作或序列化过程中的错误会被静默捕获,以避免程序崩溃。
-
核心方法
-
TriggerSave
:-
当设置被修改时,触发延迟保存操作。
-
使用计时器等待2秒后再保存,减少不必要的文件写入。
-
-
InternalSaveFile
:-
将设置字典序列化并写入二进制文件。
-
只有在有未保存的更改时(
LastCause
不为空)才会执行保存。
-
-
OnTimerExpire
:-
计时器到期时调用,触发保存操作。
-
-
Exiting
:-
在应用程序退出时调用,确保所有未保存的更改被保存。
-
使用示例
// 设置图形模式
Settings.ConfiguredGraphicMode = Settings.GraphicMode.DIB;
// 获取设置值
int timeout = Settings.GetObject("Timeout", 1000);
// 设置新值
Settings.SetObject("Timeout", 2000);
// 删除设置
Settings.DeleteObject("Timeout");
// 检查设置是否存在
bool exists = Settings.ExistObject("Timeout");
// 强制保存(例如,用于复杂对象)
Settings.SetObject("ComplexObject", new object[] { 1, 2, 3 }, true);
潜在改进
-
BinaryFormatter
的安全性:-
BinaryFormatter
被认为是不安全的,并且在.NET中已被弃用。建议使用更安全的序列化方法,如System.Text.Json
或XmlSerializer
。
-
-
错误日志记录:
-
目前错误被静默捕获,建议添加日志记录以便调试。
-
-
自定义文件路径:
-
允许配置文件路径,而不是硬编码。
-
-
异步支持:
-
使用异步文件操作(如
FileStream
与async
/await
)以避免阻塞主线程。
-
-
设置验证:
-
添加验证逻辑,确保设置值在可接受范围内。
-
总结
这个Settings
类提供了一种健壮且线程安全的方式来管理应用程序设置,支持延迟保存、版本跟踪和图形模式管理等功能。不过,可以通过替换BinaryFormatter
、增强错误处理和日志记录等方式进一步改进代码的安全性和可维护性。