diff --git a/Apewer.Windows/Internals/Interop/Kernel32.cs b/Apewer.Windows/Internals/Interop/Kernel32.cs index 7cb39ed..3b2ad7b 100644 --- a/Apewer.Windows/Internals/Interop/Kernel32.cs +++ b/Apewer.Windows/Internals/Interop/Kernel32.cs @@ -43,6 +43,9 @@ namespace Apewer.Internals.Interop [DllImport("kernel32.dll", ExactSpelling = true)] public static extern IntPtr GetCurrentProcess(); + [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern int GetCurrentThreadId(); + [DllImport("kernel32")] public static extern int GetShortPathName(string lpszLongPath, string lpszShortPath, int cchBuffer); diff --git a/Apewer.Windows/Internals/Interop/User32.cs b/Apewer.Windows/Internals/Interop/User32.cs index 8a67a21..d217567 100644 --- a/Apewer.Windows/Internals/Interop/User32.cs +++ b/Apewer.Windows/Internals/Interop/User32.cs @@ -125,6 +125,9 @@ namespace Apewer.Internals.Interop [DllImport("user32.dll")] public static extern int GetWindowTextW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder lpString, int nMaxCount); + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern int GetWindowThreadProcessId(HandleRef hWnd, out int lpdwProcessId); + /// /// /// diff --git a/Apewer.Windows/Surface/Extensions.cs b/Apewer.Windows/Surface/Extensions.cs deleted file mode 100644 index 480930b..0000000 --- a/Apewer.Windows/Surface/Extensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -#if NET40 || NET461 - -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace Apewer.Surface -{ - - /// - public static class Extensions - { - - /// 保存为 PNG 文件。 - public static byte[] SaveAsPng(this Image @this, bool disposeImage = false) - { - return ImageUtility.SaveAsBytes(@this, ImageFormat.Png, disposeImage); - } - - /// 保存为 JPEG 文件。 - public static byte[] SaveAsJpeg(this Image @this, bool disposeImage = false) - { - return ImageUtility.SaveAsBytes(@this, ImageFormat.Jpeg, disposeImage); - } - - /// - public static void BeginInvoke(this Control control, Action action) - { - if (action == null) return; - control.BeginInvoke(action as Delegate); - } - - /// - public static void Invoke(this Control control, Action action) - { - if (action == null) return; - control.Invoke(action as Delegate); - } - - } - -} - -#endif diff --git a/Apewer.Windows/Surface/FormsUtility.cs b/Apewer.Windows/Surface/FormsUtility.cs index 69e78b6..4a12d8c 100644 --- a/Apewer.Windows/Surface/FormsUtility.cs +++ b/Apewer.Windows/Surface/FormsUtility.cs @@ -23,7 +23,7 @@ namespace Apewer.Surface /// 窗体实用工具。 [SecuritySafeCritical] - public class FormsUtility + public static class FormsUtility { /// 线程锁。 @@ -76,6 +76,50 @@ namespace Apewer.Surface #endif + #region 线程 + + /// 在拥有此控件的基础窗口句柄的线程上执行指定的委托。 + public static void Invoke(this Control control, Action action) + { + if (control == null) throw new ArgumentNullException(nameof(control)); + if (action == null) throw new ArgumentNullException(nameof(action)); + + // control.Invoke(action as Delegate); + + control.Invoke(new Action(delegate () + { + action.Invoke(); + })); + } + + /// 在创建控件的基础句柄所在线程上异步执行指定委托。 + /// + public static IAsyncResult BeginInvoke(this Control control, Action action) + { + if (control == null) throw new ArgumentNullException(nameof(control)); + if (action == null) throw new ArgumentNullException(nameof(action)); + + // control.BeginInvoke(action as Delegate); + + return control.BeginInvoke(new Action(delegate () + { + action.Invoke(); + })); + } + + /// 控件属于当前线程。 + /// + public static bool OnCurrentThread(Control control) + { + if (control == null) throw new ArgumentNullException(nameof(control)); + + var controlThreadId = User32.GetWindowThreadProcessId(new HandleRef(control, control.Handle), out int _); + var currentThreadId = Kernel32.GetCurrentThreadId(); + return controlThreadId == currentThreadId; + } + + #endregion + #region 颜色。 /// 获取所有可枚举的颜色。 @@ -603,38 +647,6 @@ namespace Apewer.Surface current.Visible = false; } - /// - public static void Invoke(Control control, Action action, bool async = false) - { - if (control == null || action == null) return; - // control.Invoke(action); - if (async) - { - control.BeginInvoke(new Action(delegate () - { - action.Invoke(); - })); - } - else - { - control.Invoke(new Action(delegate () - { - action.Invoke(); - })); - } - } - - /// - public static void BeginInvoke(Control control, Action action) - { - if (control == null || action == null) return; - - control.BeginInvoke(new Action(delegate () - { - action.Invoke(); - })); - } - /// 设置窗体置顶。 public static void SetTopMost(IntPtr form, bool value = true) { @@ -1041,6 +1053,86 @@ namespace Apewer.Surface #endregion + #region WndProc + + /// 允许鼠标调整窗体大小。此方法对 FormBorderStyle 为 None 的窗体生效。 + /// 已处理事件。 + /// + /// + public static bool AllowResizeForm(this Form form, ref Message m, int padding = 4) + { + if (form == null) throw new ArgumentNullException(nameof(form)); + if (form.FormBorderStyle != FormBorderStyle.None) return false; + + if (padding < 0) throw new ArgumentOutOfRangeException(nameof(padding)); + + const int HT_LEFT = 10; + const int HT_RIGHT = 11; + const int HT_TOP = 12; + const int HT_TOP_LEFT = 13; + const int HT_TOP_RIGHT = 14; + const int HT_BOTTOM = 15; + const int HT_BOTTOM_LEFT = 16; + const int HT_BOTTOM_RIGHT = 17; + + switch (m.Msg) + { + case 0x0084: + var clientSize = form.ClientSize; + var screenPoint = new System.Drawing.Point((int)m.LParam & 0xFFFF, (int)m.LParam >> 16 & 0xFFFF); + var point = form.PointToClient(screenPoint); + if (point.X <= padding) + { + if (point.Y <= padding) m.Result = (IntPtr)HT_TOP_LEFT; + else if (point.Y >= clientSize.Height - padding) m.Result = (IntPtr)HT_BOTTOM_LEFT; + else m.Result = (IntPtr)HT_LEFT; + return true; + } + else if (point.X >= clientSize.Width - padding) + { + if (point.Y <= padding) m.Result = (IntPtr)HT_TOP_RIGHT; + else if (point.Y >= clientSize.Height - padding) m.Result = (IntPtr)HT_BOTTOM_RIGHT; + else m.Result = (IntPtr)HT_RIGHT; + return true; + } + else if (point.Y <= padding) + { + m.Result = (IntPtr)HT_TOP; + return true; + } + else if (point.Y >= clientSize.Height - padding) + { + m.Result = (IntPtr)HT_BOTTOM; + return true; + } + break; + } + + return false; + } + + /// 允许移动窗体。左键点击时修改消息,认为鼠标点在非客户区(标题栏)。 + /// 已处理事件。 + /// + public static bool AllowMoveForm(this Form form, ref Message m) + { + if (form == null) throw new ArgumentNullException(nameof(form)); + + switch (m.Msg) + { + // 左键点击时修改消息,认为鼠标点在非客户区(标题栏)。 + case 0x0201: + m.Msg = 0x00A1; + m.LParam = IntPtr.Zero; + m.WParam = new IntPtr(2); + return true; + } + + return false; + } + + #endregion + } } diff --git a/Apewer.Windows/Surface/ImageUtility.cs b/Apewer.Windows/Surface/ImageUtility.cs index fa48253..25def4f 100644 --- a/Apewer.Windows/Surface/ImageUtility.cs +++ b/Apewer.Windows/Surface/ImageUtility.cs @@ -21,10 +21,12 @@ namespace Apewer.Surface internal static byte[] EmptyBytes { get { return new byte[0]; } } - /// 保存图像到文件,失败时返回 NULL 值。 - public static byte[] SaveAsBytes(Image image, ImageFormat format, bool dispose = false) + /// 保存图像到文件。 + /// + public static byte[] SaveAs(this Image image, ImageFormat format) { - if (image == null) return EmptyBytes; + if (image == null) throw new ArgumentNullException(nameof(image)); + if (format == null) throw new ArgumentNullException(nameof(format)); var memory = new MemoryStream(); var bytes = null as byte[]; @@ -35,20 +37,21 @@ namespace Apewer.Surface } catch { } memory.Dispose(); - if (dispose) image.Dispose(); return bytes; } - /// 保存为 PNG 文件,失败时返回 NULL 值。 - public static byte[] SaveAsPng(Image image, bool dispose = false) + /// 保存为 PNG 文件。 + /// + public static byte[] SaveAsPng(this Image image) { - return SaveAsBytes(image, ImageFormat.Png, dispose); + return SaveAs(image, ImageFormat.Png); } - /// 保存为 JPEG 文件,失败时返回 NULL 值。 - public static byte[] SaveAsJpeg(Image image, bool dispose = false) + /// 保存为 JPEG 文件。 + /// + public static byte[] SaveAsJpeg(this Image image) { - return SaveAsBytes(image, ImageFormat.Jpeg, dispose); + return SaveAs(image, ImageFormat.Jpeg); } /// 调整图像尺寸,生成新图像。 diff --git a/Apewer.Windows/_Extensions.cs b/Apewer.Windows/_Extensions.cs deleted file mode 100644 index 4da20c0..0000000 --- a/Apewer.Windows/_Extensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Apewer; -using Apewer.Surface; -using System; -using System.Windows.Forms; - -/// 扩展方法。 -public static class Extensions_Apewer_Windows -{ - - #region Surface - -#if NETFX || NETCORE - - /// - public static void Invoke(this Control @this, Action action, bool async = false) => FormsUtility.Invoke(@this, action, async); - -#endif - - #endregion - -} \ No newline at end of file diff --git a/Apewer/Apewer.props b/Apewer/Apewer.props index 9aad0c6..67efd36 100644 --- a/Apewer/Apewer.props +++ b/Apewer/Apewer.props @@ -9,7 +9,7 @@ Apewer Apewer Libraries - 6.7.5 + 6.7.6 diff --git a/Apewer/BytesSet.cs b/Apewer/BytesSet.cs deleted file mode 100644 index a59af39..0000000 --- a/Apewer/BytesSet.cs +++ /dev/null @@ -1,328 +0,0 @@ -using Apewer.Internals; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace Apewer -{ - - /// - public class BytesSet - { - - private Dictionary _dict = new Dictionary(); - - private volatile Func _inbound = null; - private volatile Func _outbound = null; - - /// - public byte[] this[string key] - { - get { return GetValue(key); } - set { SetValue(key, value); } - } - - /// 设置或获取值入站的函数,设置时字典必须为空。设置为 Null 将忽略入站函数。 - public Func Inbound - { - get { lock (_dict) return _inbound; } - set { lock (_dict) _inbound = value; } - } - - /// 设置或获取值出站的函数。设置为 Null 将忽略出站函数。 - public Func Outbound - { - get { lock (_dict) return _outbound; } - set { lock (_dict) _outbound = value; } - } - - /// - public int Count - { - get - { - var count = 0; - lock (_dict) { count = _dict.Count; } - return count; - } - } - - /// - public List Keys - { - get - { - var list = new List(); - lock (_dict) - { - list.AddRange(_dict.Keys); - } - return list; - } - } - - /// - public BytesSet() { } - - /// - public BytesSet(Func inbound, Func outbound) - { - _inbound = inbound; - _outbound = outbound; - } - - /// - public void Clear() - { - lock (_dict) - { - foreach (var key in _dict.Keys) - { - _dict[key] = null; - } - _dict.Clear(); - } - } - - /// - public bool Import(byte[] data) - { - var memory = new MemoryStream(); - lock (data) - { - if (data != null) memory.Write(data, 0, data.Length); - BytesUtility.ResetPosition(memory); - } - if (memory.Length < 4) return false; - var count = 0; - var first = true; - while (true) - { - if (!CanRead(memory, 4)) break; - if (first) - { - var buffer = new byte[4]; - memory.Read(buffer, 0, 4); - count = GetInt32(buffer); - - first = false; - } - else - { - - // Read Key Length - var keylength = 0; - { - if (!CanRead(memory, 4)) break; - var buffer = new byte[4]; - memory.Read(buffer, 0, 4); - keylength = GetInt32(buffer); - } - - // Read Key Data - var key = Constant.EmptyString; - if (keylength > 1) - { - if (!CanRead(memory, keylength)) break; - var buffer = new byte[keylength]; - memory.Read(buffer, 0, keylength); - key = BytesUtility.ToText(buffer); - } - - // Read Value Length - var valuelength = 0; - { - if (!CanRead(memory, 4)) break; - var buffer = new byte[4]; - memory.Read(buffer, 0, 4); - valuelength = GetInt32(buffer); - } - - // Read Key Data - var value = Constant.EmptyBytes; - if (valuelength > 1) - { - if (!CanRead(memory, valuelength)) break; - var buffer = new byte[valuelength]; - memory.Read(buffer, 0, valuelength); - value = BytesUtility.Clone(buffer); - } - - if (_dict.ContainsKey(key)) continue; - _dict.Add(key, value); - if (_dict.Count >= count) break; - } - } - return count == _dict.Count; - } - - /// - public byte[] Export() - { - var memory = new MemoryStream(); - lock (_dict) - { - var count = _dict.Count; - var countbytes = GetBytes(count); - memory.Write(countbytes, 0, countbytes.Length); - foreach (var pair in _dict) - { - var keydata = BytesUtility.FromText(pair.Key); - var keycount = GetBytes(keydata.Length); - memory.Write(keycount, 0, keycount.Length); - if (keydata.Length > 0) memory.Write(keydata, 0, keydata.Length); - - var valuedata = pair.Value ?? Constant.EmptyBytes; - var valuecount = GetBytes(valuedata.Length); - memory.Write(valuecount, 0, valuecount.Length); - if (valuedata.Length > 0) memory.Write(valuedata, 0, valuedata.Length); - } - } - var data = memory.ToArray(); - memory.Dispose(); - return data; - } - - /// - public bool Contains(string key) - { - if (key == null) return false; - var contains = false; - lock (_dict) - { - contains = _dict.ContainsKey(key); - } - return contains; - } - - /// - public byte[] GetValue(string key) - { - var k = key; - var v = Constant.EmptyBytes; - if (k == null) return v; - lock (_dict) - { - if (_dict.ContainsKey(k)) v = _dict[k]; - } - if (_outbound != null) - { - v = _outbound(v); - if (v == null) v = Constant.EmptyBytes; - } - return v; - } - - /// - public bool SetValue(string key, byte[] value) - { - var k = key; - var v = Constant.EmptyBytes; - if (k == null) return false; - lock (value) - { - if (value != null) v = BytesUtility.Clone(value); - } - if (_inbound != null) - { - v = _inbound(v); - if (v == null) v = Constant.EmptyBytes; - } - lock (_dict) - { - if (_dict.ContainsKey(k)) _dict.Remove(k); - _dict.Add(k, v); - } - return true; - } - - private static bool CanRead(Stream stream, int length) - { - if (length < 0) return false; - if (stream == null) return false; - if (stream.CanRead == false) return false; - if (stream.Position + length > stream.Length) return false; - return true; - } - - /// Int32 -> Byte[] - private static byte[] GetBytes(int value) - { - const int t3 = 256 * 256 * 256; - const int t2 = 256 * 256; - const int t1 = 256; - - byte[] bs = { 0, 0, 0, 0 }; - - if (value >= 0) - { - int vint = value; - - bs[0] = (byte)(vint / t3); - vint = vint % t3; - - bs[1] = (byte)(vint / t2); - vint = vint % t2; - - bs[2] = (byte)(vint / t1); - vint = vint % t1; - - bs[3] = (byte)(vint); - } - else - { - int minusInt = Math.Abs(value + 1); - var minusBytes = GetBytes(minusInt); - bs[0] = (byte)(255 - minusBytes[0]); - bs[1] = (byte)(255 - minusBytes[1]); - bs[2] = (byte)(255 - minusBytes[2]); - bs[3] = (byte)(255 - minusBytes[3]); - } - - return bs; - } - - /// Byte[] -> Int32 - private static Int32 GetInt32(byte[] value) - { - if (value.Length == 4) - { - const int t3 = 256 * 256 * 256; - const int t2 = 256 * 256; - const int t1 = 256; - - if (value[0] <= 127) - { - int[] vis = { 0, 0, 0, 0 }; - vis[0] = value[0] * t3; - vis[1] = value[1] * t2; - vis[2] = value[2] * t1; - vis[3] = value[3]; - int vr = vis[0] + vis[1] + vis[2] + vis[3]; - return vr; - } - else - { - if ((value[0] == 128) && (value[1] == 0) && (value[2] == 0) && (value[3] == 0)) - { - return int.MinValue; - } - else - { - var bytes = new byte[4]; - bytes[0] = (byte)(255 - value[0]); - bytes[1] = (byte)(255 - value[1]); - bytes[2] = (byte)(255 - value[2]); - bytes[3] = (byte)(255 - value[3]); - int vminusint = 0 - 1 - GetInt32(bytes); - return vminusint; - } - } - } - return 0; - } - - } - -} diff --git a/Apewer/BytesUtility.cs b/Apewer/BytesUtility.cs index cbb86ee..4b76127 100644 --- a/Apewer/BytesUtility.cs +++ b/Apewer/BytesUtility.cs @@ -13,7 +13,7 @@ namespace Apewer { /// 二进制。 - public class BytesUtility + public static class BytesUtility { /// 空字节数组,每次获取都将创建新的引用。 @@ -42,25 +42,19 @@ namespace Apewer #region Bytes Array /// 克隆字节数组。当源为 NULL 时获取零元素字节数组。 - public static byte[] Clone(byte[] bytes) + public static byte[] Clone(this byte[] bytes) { - if (bytes == null || bytes.LongLength < 0L) return Empty; - var result = new byte[bytes.LongLength]; - bytes.CopyTo(result, 0L); + if (bytes == null) return Empty; + var length = bytes.Length; + if (length < 1) return Empty; + var result = new byte[length]; + Buffer.BlockCopy(bytes, 0, result, 0, length); return result; } - /// 创建数组,元素值为零。 - public static byte[] ZeroArray(int length = 0) - { - if (length < 1) return new byte[0]; - var array = new byte[length]; - for (int i = 0; i < length; i++) array[i] = 0; - return array; - } - - /// 所有字节取反。 - public static byte[] Adverse(byte[] bytes) + /// 每个字节取反。 + /// value = 255 - value + public static byte[] Adverse(this byte[] bytes) { if (bytes == null || bytes.LongLength < 1L) return Empty; var adverse = new byte[bytes.LongLength]; @@ -69,41 +63,42 @@ namespace Apewer } /// 确定此字节数组实例的开头是否与指定的字节数组匹配。 - public static bool StartsWith(byte[] bytes, params byte[] head) + public static bool StartsWith(this byte[] bytes, params byte[] head) { - var data = bytes; + // 头部为空,视为匹配。 + if (head == null) return true; + var length = head.Length; + if (length < 1) return true; - if (data == null) return false; - if (head == null) return false; - - var datalength = data.LongLength; - var headlength = head.LongLength; - if (datalength < headlength) return false; - if (headlength < 1L) return true; + // 样本为空,视为不匹配。 + if (bytes == null) return false; + if (bytes.Length < length) return false; - for (long i = 0; i < head.LongLength; i++) + // 对比头部字节。 + for (var i = 0; i < length; i++) { - if (data[i] != head[i]) return false; + if (bytes[i] != head[i]) return false; } return true; } /// 确定此字节数组实例的结尾是否与指定的字节数组匹配。 - public static bool EndsWith(byte[] bytes, params byte[] end) + public static bool EndsWith(this byte[] bytes, params byte[] foot) { - if (bytes == null) return false; - if (end == null) return false; + // 尾部为空,视为匹配。 + if (foot == null) return true; + var length = foot.Length; + if (length < 1) return true; - var dataLength = bytes.LongLength; - var endLength = end.LongLength; - if (dataLength < endLength) return false; - if (endLength < 1L) return true; + // 样本为空,视为不匹配。 + if (bytes == null) return false; + if (bytes.Length < length) return false; - for (long i = 0; i < endLength; i++) + // 对比尾部字节。 + var offset = bytes.Length - length; + for (var i = 0; i < length; i++) { - var dataindex = dataLength - i - 1; - var headindex = endLength - i - 1; - if (bytes[dataindex] != end[headindex]) return false; + if (bytes[offset + i] != foot[i]) return false; } return true; } @@ -142,7 +137,7 @@ namespace Apewer public static byte[] Append(byte[] head, params byte[] bytes) => Merge(head, bytes); /// 为文本数据添加 BOM 字节,若已存在则忽略。 - public static byte[] AddTextBom(params byte[] bytes) + public static byte[] AddTextBom(this byte[] bytes) { var bom = new byte[] { 0xEF, 0xBB, 0xBF }; if (bytes == null || bytes.LongLength < 1L) return bom; @@ -152,7 +147,7 @@ namespace Apewer } /// 去除文本数据的 BOM 字节,若不存在则忽略。 - public static byte[] WipeTextBom(byte[] bytes) + public static byte[] WipeTextBom(this byte[] bytes) { if (bytes == null) return Empty; var hasBom = (bytes.Length >= 3) && (bytes[0] == 0xEF) && (bytes[1] == 0xBB) && (bytes[2] == 0xBF); @@ -171,7 +166,7 @@ namespace Apewer #region Text /// 将字节数组转换为十六进制文本。 - public static string ToHex(params byte[] bytes) + public static string ToHex(this byte[] bytes) { int length = bytes.Length; if (length > 0) @@ -188,7 +183,7 @@ namespace Apewer } /// 将十六进制文本转换为字节数组。 - public static byte[] FromHex(string hex) + public static byte[] FromHex(this string hex) { if (string.IsNullOrEmpty(hex) || hex.Length < 2) return Empty; if (hex.Length % 2 != 0) return Empty; @@ -208,11 +203,9 @@ namespace Apewer return bytes; } - /// 将字节数组格式化为大写十六进制字符串。 - public static string ToX2(params byte[] bytes) => ToX2(true, bytes); - /// 将字节数组格式化为十六进制字符串,可指定大小写。 - public static string ToX2(bool upper, params byte[] bytes) + /// 例:D41D8CD98F00B204E9800998ECF8427E + public static string ToX2(this byte[] bytes, bool upper = true) { if (bytes == null) return ""; var length = bytes.Length; @@ -233,7 +226,7 @@ namespace Apewer /// Byte[] -> Base64 public static string ToBase64(params byte[] bytes) { - if (bytes.Length < 1) return Constant.EmptyString; + if (bytes == null || bytes.Length < 1) return Constant.EmptyString; try { return Convert.ToBase64String(bytes); } catch { return Constant.EmptyString; } } @@ -859,123 +852,97 @@ namespace Apewer { try { - var algorithm = new T(); - var result = algorithm.ComputeHash(bytes); - algorithm.Clear(); -#if !NET20 - algorithm.Dispose(); -#endif - return result; + using (var algorithm = new T()) + { + var result = algorithm.ComputeHash(bytes); + algorithm.Clear(); + return result; + } } catch { } } return Empty; } - private static byte[] ComputeHash(Stream stream, bool dispose, Action progress) where T : HashAlgorithm, new() + private static byte[] ComputeHash(Stream stream, Action progress) where T : HashAlgorithm, new() { if (progress == null) { - if (stream != null) + + using (var algorithm = new T()) { - var result = Empty; - try + if (stream == null) { - var algorithm = new T(); - result = algorithm.ComputeHash(stream); + var result = algorithm.ComputeHash(Empty); algorithm.Clear(); -#if !NET20 - algorithm.Dispose(); -#endif + return result; + } + else + { + var result = algorithm.ComputeHash(stream); + algorithm.Clear(); + return result; } - catch { } - if (dispose) stream.Dispose(); - return result; } - return Empty; } else { if (stream == null) return Empty; // 初始化。 - var validcallback = progress != null; - var capacity = DefaultBuffer; - var buffer = new byte[capacity]; - var algorithm = new T(); - algorithm.Initialize(); - - // 读取。 - var failed = false; - while (true) + using (var algorithm = new T()) { - var read = 0; - try { read = stream.Read(buffer, 0, capacity); } - catch { failed = true; } + algorithm.Initialize(); - if (read < capacity) + // 读取。 + var count = 0; + var input = new byte[DefaultBuffer]; + var output = new byte[DefaultBuffer]; + while (true) { - if (read < 1) + count = stream.Read(input, 0, DefaultBuffer); + + if (count < DefaultBuffer) { - algorithm.TransformFinalBlock(new byte[0], 0, 0); + algorithm.TransformFinalBlock(input, 0, count); + break; } else { - algorithm.TransformFinalBlock(buffer, 0, Convert.ToInt32(read)); + algorithm.TransformBlock(input, 0, count, output, 0); } - break; } - else - { - algorithm.TransformBlock(buffer, 0, Convert.ToInt32(read), buffer, 0); - } - } - if (failed) - { - algorithm.Clear(); -#if !NET20 - algorithm.Dispose(); -#endif - if (dispose) stream.Dispose(); - return Empty; - } - else - { var result = algorithm.Hash; algorithm.Clear(); -#if !NET20 - algorithm.Dispose(); -#endif - if (dispose) stream.Dispose(); return result; } } } /// 获取 MD5 值。 - public static byte[] MD5(params byte[] bytes) => ComputeHash(bytes); + public static byte[] MD5(this byte[] bytes) => ComputeHash(bytes); /// 获取 MD5 值。 - public static byte[] MD5(Stream stream, Action progress = null) => ComputeHash(stream, false, progress); + public static byte[] MD5(this Stream stream, Action progress = null) => ComputeHash(stream, progress); /// 获取 SHA1 值。 - public static byte[] SHA1(params byte[] bytes) => ComputeHash(bytes); + public static byte[] SHA1(this byte[] bytes) => ComputeHash(bytes); /// 获取 SHA1 值。 - public static byte[] SHA1(Stream stream, Action progress = null) => ComputeHash(stream, false, progress); + public static byte[] SHA1(this Stream stream, Action progress = null) => ComputeHash(stream, progress); /// 获取 SHA256 值。 - public static byte[] SHA256(params byte[] bytes) => ComputeHash(bytes); + public static byte[] SHA256(this byte[] bytes) => ComputeHash(bytes); /// 获取 SHA256 值。 - public static byte[] SHA256(Stream stream, Action progress = null) => ComputeHash(stream, false, progress); + public static byte[] SHA256(this Stream stream, Action progress = null) => ComputeHash(stream, progress); /// 获取 SHA512 值。 - public static byte[] SHA512(params byte[] bytes) => ComputeHash(bytes); + public static byte[] SHA512(this byte[] bytes) => ComputeHash(bytes); /// 获取 SHA512 值。 - public static byte[] SHA512(Stream stream, Action progress = null) => ComputeHash(stream, false, progress); + public static byte[] SHA512(this Stream stream, Action progress = null) => ComputeHash(stream, progress); #endregion diff --git a/Apewer/CipherSet.cs b/Apewer/CipherSet.cs deleted file mode 100644 index 2934d6d..0000000 --- a/Apewer/CipherSet.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Apewer -{ - - internal class CipherSet - { - } - -} diff --git a/Apewer/CollectionUtility.cs b/Apewer/CollectionUtility.cs index 29d530e..5d5ec57 100644 --- a/Apewer/CollectionUtility.cs +++ b/Apewer/CollectionUtility.cs @@ -396,6 +396,76 @@ namespace Apewer return ab.Export(); } + /// 在数组尾部增加元素,生成新数组,不修改原数组。 + /// 增加元素后的新数组。 + public static T[] Push(this T[] array, T item) + { + if (array == null) return new T[] { item }; + + var length = array.Length; + var newArray = new T[length + 1]; + if (length > 0) System.Array.Copy(array, 0, newArray, 0, length); + newArray[length] = item; + return newArray; + } + + /// 在数组尾部增加元素,生成新数组,不修改原数组。 + /// 增加元素后的新数组。 + public static T[] Push(this T[] array, T[] items) + { + if (array == null) return items ?? new T[0]; + if (items == null) + { + var result = new T[array.Length]; + array.CopyTo(result, 0); + return result; + } + else + { + var length1 = array.Length; + var length2 = items.Length; + var result = new T[length1 + length2]; + if (length1 > 0) System.Array.Copy(array, 0, result, 0, length1); + if (length2 > 0) System.Array.Copy(items, 0, result, length1, length2); + return result; + } + } + + /// 在数组头部增加元素,生成新数组,不修改原数组。 + /// 增加元素后的新数组。 + public static T[] Unshift(this T[] array, T item) + { + if (array == null) return new T[] { item }; + + var length = array.Length; + var newArray = new T[length + 1]; + newArray[0] = item; + if (length > 0) System.Array.Copy(array, 0, newArray, 1, length); + return newArray; + } + + /// 在数组头部增加元素,生成新数组,不修改原数组。 + /// 增加元素后的新数组。 + public static T[] Unshift(this T[] array, T[] items) + { + if (array == null) return items ?? new T[0]; + if (items == null) + { + var result = new T[array.Length]; + array.CopyTo(result, 0); + return result; + } + else + { + var length1 = array.Length; + var length2 = items.Length; + var result = new T[length1 + length2]; + if (length2 > 0) System.Array.Copy(items, 0, result, 0, length2); + if (length1 > 0) System.Array.Copy(array, 0, result, length2, length1); + return result; + } + } + #endregion #region 排序 diff --git a/Apewer/Json.cs b/Apewer/Json.cs index 605e934..ab66893 100644 --- a/Apewer/Json.cs +++ b/Apewer/Json.cs @@ -50,9 +50,19 @@ namespace Apewer #endregion - #region 构造。 + #region 消息 + + private const string ValueIsNotSupported = "指定的值类型不受支持"; + private const string IsNotJsonObject = "当前实例不是 Json 对象。"; + private const string IsNotJsonArray = "当前实例不是 Json 数组。"; + private const string InvalidIndex = "未指定有效的索引。"; + private const string IndexLessZero = "指定的索引小于 0,无效。"; + private const string IndexGraterMax = "指定的索引超出了最大值,无效。"; + private const string IndexGraterCount = "指定的索引超出了数量,无效。"; - #region 基础。 + #endregion + + #region JToken [NonSerialized] private JToken _jtoken = null; @@ -69,8 +79,57 @@ namespace Apewer [NonSerialized] private JValue _jvalue = null; + static JToken ToJToken(object value) + { + if (value == null) return new JValue(null, JTokenType.Null); + + var type = value.GetType(); + if (RuntimeUtility.IsNullableType(type)) value = RuntimeUtility.GetNullableValue(value); + + if (value == null) return new JValue(null, JTokenType.Null); + if (value is Json json) return json?._jtoken ?? new JValue(null, JTokenType.Null); + if (value is DateTime dt) return new JValue(SerializeDateTime(dt)); + + if (value is string) return new JValue(value); + if (value is bool) return new JValue(value); + + if (value is byte) return new JValue(value); + if (value is sbyte) return new JValue(value); + if (value is short) return new JValue(value); + if (value is ushort) return new JValue(value); + if (value is int) return new JValue(value); + if (value is uint) return new JValue(value); + if (value is long) return new JValue(value); + if (value is ulong uint64) return (uint64 > int.MaxValue) ? new JValue(uint64.ToString()) : new JValue(Convert.ToInt64(uint64)); + + if (value is float) return new JValue(value); + if (value is double) return new JValue(value); + if (value is decimal) return new JValue(value); + + var from = From(value); + if (from != null) return from._jtoken; + throw new ArgumentException(ValueIsNotSupported); + } + + static object ParseJToken(JToken jtoken) + { + if (jtoken == null) return null; + if (jtoken is JValue jvalue) return jvalue.Value; + if (jtoken is JObject) return new Json(jtoken); + if (jtoken is JArray) return new Json(jtoken); + if (jtoken is JProperty jproperty) + { + var value = jproperty.Value; + if (value == null) return null; + return ParseJToken(value); + } + throw new InvalidOperationException($"Json 类型 {jtoken.Type} 不支持解析。"); + } + #endregion + #region 构造。 + #region Reset /// 重置当前对象为空。 @@ -190,19 +249,10 @@ namespace Apewer #region 属性。 /// 用于兼容 SimpleJson 的操作。 - public string this[string name] - { - get - { - var property = GetProperty(name); - if (property == null) return ""; - return property.ToString(); - } - set - { - SetProperty(name, value ?? ""); - } - } + public string this[string name] { get => GetProperty(name)?.ToString() ?? ""; set => SetProperty(name, value ?? ""); } + + /// 获取或设置数组的元素。 + public object this[int index] { get => GetItem(index); set => SetItem(index, value); } private JTokenType TokenType { @@ -244,9 +294,7 @@ namespace Apewer public string Type { get { return TokenType.ToString(); } } /// 实例有效。 - public - bool Available - { get { return _jtoken != null && TokenType != JTokenType.None; } } + public bool Available { get { return _jtoken != null && TokenType != JTokenType.None; } } /// 获取当前实例的值,当为 Json 格式时缩进。 public string Lucid { get { return ToString(true); } } @@ -921,6 +969,37 @@ namespace Apewer } } + /// 获取数组的元素。 + /// + /// + public object GetItem(int index) + { + if (!IsArray) throw new InvalidOperationException(IsNotJsonArray); + + if (index < 0) throw new ArgumentOutOfRangeException(nameof(index), IndexLessZero); + var jarray = _jtoken as JArray; + if (index >= jarray.Count) throw new ArgumentOutOfRangeException(nameof(index), IndexGraterCount); + + var item = jarray[index]; + return ParseJToken(item); + } + + /// 设置数组的元素。 + /// + /// + /// + public void SetItem(int index, object value) + { + if (!IsArray) throw new InvalidOperationException(IsNotJsonArray); + + if (index < 0) throw new ArgumentOutOfRangeException(nameof(index), IndexLessZero); + var jarray = _jtoken as JArray; + while (jarray.Count < index + 1) jarray.Add(null); + + var jtoken = ToJToken(value); + jarray.SetItem(index, jtoken); + } + #endregion #region Property @@ -1817,13 +1896,17 @@ namespace Apewer { if (pt.Equals(typeof(DateTime))) { - try + if (AllowException) { - setter.Invoke(entity, new object[] { DeserializeDateTime(value as string) }); + setter.Invoke(entity, new object[] { DeserializeDateTime(value) }); } - catch (Exception exception) + else { - if (AllowException) throw exception; + try + { + setter.Invoke(entity, new object[] { DeserializeDateTime(value) }); + } + catch { } } } else if (pt.Equals(typeof(string))) @@ -1888,39 +1971,202 @@ namespace Apewer /// public override bool TryGetMember(GetMemberBinder binder, out object result) { - var contains = false; - if (IsObject) + if (!IsObject) throw new InvalidOperationException(IsNotJsonObject); + + var contains = _jobject.ContainsKey(binder.Name); + if (!contains) { - var property = GetProperty(binder.Name); - contains = property != null; - result = contains ? property.Value : null; - return contains; + result = null; + return true; } - result = null; - return contains; + var jtoken = _jobject.GetValue(binder.Name); + result = ParseJToken(jtoken); + return true; } /// public override bool TrySetMember(SetMemberBinder binder, object value) { - var name = binder.Name; - - if (value == null) return SetProperty(name); - if (value is Json) return SetProperty(name, (Json)value); - if (value is string) return SetProperty(name, (string)value); - if (value is bool) return SetProperty(name, (bool)value); - if (value is byte) return SetProperty(name, (byte)value); - if (value is sbyte) return SetProperty(name, (sbyte)value); - if (value is short) return SetProperty(name, (short)value); - if (value is ushort) return SetProperty(name, (ushort)value); - if (value is int) return SetProperty(name, (int)value); - if (value is uint) return SetProperty(name, (uint)value); - if (value is long) return SetProperty(name, (long)value); - if (value is float) return SetProperty(name, (float)value); - if (value is double) return SetProperty(name, (double)value); - if (value is decimal) return SetProperty(name, (decimal)value); + if (!IsObject) throw new InvalidOperationException(IsNotJsonObject); + var contains = _jobject.ContainsKey(binder.Name); + if (contains) + { + _jobject[binder.Name] = ToJToken(value); + return true; + } + else + { + _jobject.Add(binder.Name, ToJToken(value)); + return true; + } + } + + private Class DynamicIndex(object[] indexes, Func stringCallback, Func intCallback) + { + var index = indexes[0]; + if (index != null) + { + if (IsObject) + { + if (index is string name) return new Class(stringCallback.Invoke(name)); + } + if (IsArray) + { + if (index is int || index is short || index is byte || index is ushort || index is sbyte) + { + var int32 = int.Parse(index.ToString()); + if (int32 < 0) throw new ArgumentOutOfRangeException("index", IndexLessZero); + return new Class(intCallback(int32)); + } + else if (index is long int64) + { + if (int64 < 0) throw new ArgumentOutOfRangeException("index", IndexLessZero); + if (int64 > int.MaxValue) throw new ArgumentOutOfRangeException("index", IndexGraterMax); + var int32 = Convert.ToInt32(int64); + return new Class(intCallback(int32)); + } + else if (index is uint uint32) + { + if (uint32 > int.MaxValue) throw new ArgumentOutOfRangeException("index", IndexGraterMax); + var int32 = Convert.ToInt32(uint32); + return new Class(intCallback(int32)); + } + else if (index is ulong uint64) + { + if (uint64 > int.MaxValue) throw new ArgumentOutOfRangeException("index", IndexGraterMax); + var int32 = Convert.ToInt32(uint64); + return new Class(intCallback(int32)); + } + } + } + + throw new InvalidOperationException(InvalidIndex); + } + + /// + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + { + var box = DynamicIndex(indexes, name => + { + var property = GetProperty(name); + return ParseJToken(property?._jtoken); + }, index => + { + return GetItem(index); + }); + if (box == null) + { + result = null; + return false; + } + else + { + result = box.Value; + return true; + } + } + + /// + public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) + { + var box = DynamicIndex(indexes, name => + { + _jobject[name] = ToJToken(value); + return true; + }, index => + { + SetItem(index, value); + return true; + }); + + return box != null; + } + + /// + public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) + { + switch (binder.CallInfo.ArgumentNames.First().Lower()) + { + case "tostring": + if (args == null) + { + result = ToString(); + return true; + } + else + { + switch (args.Length) + { + case 0: + result = ToString(); + return true; + case 1: + if (args[0] == null) + { + result = null; + return false; + } + else + { + if (args[0] is bool indented) + { + result = ToString(indented); + return true; + } + else if (args[0] is byte int8) + { + result = ToString(int8 == 1); + return true; + } + else if (args[0] is short int16) + { + result = ToString(int16 == 1); + return true; + } + else if (args[0] is int int32) + { + result = ToString(int32 == 1); + return true; + } + else if (args[0] is long int64) + { + result = ToString(int64 == 1); + return true; + } + else + { + result = null; + return false; + } + } + default: + result = null; + return false; + } + } + default: + result = null; + return false; + } + } + + /// + public override bool TryConvert(ConvertBinder binder, out object result) + { + if (binder.Type.Equals(typeof(Json))) + { + result = this; + return true; + } + if (binder.Type.Equals(typeof(string))) + { + result = ToString(); + return true; + } + + result = null; return false; } @@ -2247,29 +2493,40 @@ namespace Apewer #region DateTime + static Func _datetime_serializer = null; + static Func _datetime_deserializer = null; + /// 自定义 DateTime 序列化程序。 - public static Func DateTimeSerializer { get; set; } + public static Func DateTimeSerializer { get => _datetime_serializer; set => _datetime_serializer = value; } /// 自定义 DateTime 反序列化程序。 - public static Func DateTimeDeserializer { get; set; } + public static Func DateTimeDeserializer { get => _datetime_deserializer; set => _datetime_deserializer = value; } /// 序列化 DateTime 实例。 public static string SerializeDateTime(DateTime dateTime) { - var serializer = DateTimeSerializer; + var serializer = _datetime_serializer; if (serializer != null) return serializer.Invoke(dateTime); return ClockUtility.Lucid(dateTime); } /// 序列化 DateTime 实例。 - public static DateTime DeserializeDateTime(string text) + public static DateTime DeserializeDateTime(object value) { - var deserializer = DateTimeDeserializer; - if (deserializer != null) return deserializer.Invoke(text); + var deserializer = _datetime_deserializer; + if (deserializer != null) return deserializer.Invoke(value); + + try + { + if (value is DateTime dt) return dt; - var ndt = ClockUtility.Parse(text); - return ndt == null ? default(DateTime) : ndt.Value; + var text = value.ToString(); + var parse = ClockUtility.Parse(text); + if (parse != null) return parse.Value; + } + catch { } + return default; } #endregion diff --git a/Apewer/Network/Extension.cs b/Apewer/Network/Extension.cs index d705c50..c0da3c6 100644 --- a/Apewer/Network/Extension.cs +++ b/Apewer/Network/Extension.cs @@ -134,7 +134,7 @@ namespace Apewer.Network { if (reply == null) return null; - var buffer = reply.Buffer.X2(); + var buffer = BytesUtility.ToX2(reply.Buffer); if (buffer.Replace("0", "").IsEmpty()) buffer = null; var json = Json.NewObject(); diff --git a/Apewer/NumberUtility.cs b/Apewer/NumberUtility.cs index 32464e0..132fa37 100644 --- a/Apewer/NumberUtility.cs +++ b/Apewer/NumberUtility.cs @@ -187,17 +187,254 @@ namespace Apewer #endregion + #region 平均值 + + /// 平均值。 + public static float Average(params float[] values) + { + if (values == null) return 0F; + + var amount = 0F; + foreach (var i in values) amount += i; + return amount / values.Length; + } + /// 平均值。 public static double Average(params double[] values) { - var amount = 0d; + if (values == null) return 0D; + + var amount = 0D; foreach (var i in values) amount += i; return amount / Convert.ToDouble(values.Length); } - #region 最小值、最大值。 + /// 平均值。 + public static decimal Average(params decimal[] values) + { + if (values == null) return 0M; + + var amount = 0M; + foreach (var i in values) amount += i; + return amount / Convert.ToDecimal(values.Length); + } + + #endregion + + #region 标准差 + + /// 计算标准差。 + /// 值。 + /// 计算样本标准差,分母为 n - 1。此值为 false 时计算总体标准差。 + public static double Stdev(this IEnumerable values, bool sample = false) + { + if (values == null) return 0; + + var count = 0; + var sum = 0D; + var up = 0D; + if (values is double[] array) + { + count = array.Length; + if (count < 1) return 0; + for (var i = 0; i < count; i++) sum += array[i]; + + var avg = sum / count; + for (var i = 0; i < count; i++) up += (array[i] - avg) * (array[i] - avg); + } + else if (values is IList list) + { + count = list.Count; + if (count < 1) return 0; + for (var i = 0; i < count; i++) sum += list[i]; + + var avg = sum / count; + for (var i = 0; i < count; i++) up += (list[i] - avg) * (list[i] - avg); + } + else + { + foreach (var value in values) + { + sum += value; + count++; + } + if (count < 1) return 0; + + var avg = sum / count; + foreach (var value in values) + { + up += (value - avg) * (value - avg); + count++; + } + } + + // 样本标准差,最少要 2 个值。 + if (sample) + { + var down = count - 1; + if (down == 0) return 0; + + var sigma = Math.Sqrt(up / down); + return sigma; + } + else + { + var sigma = Math.Sqrt(up / count); + return sigma; + } + } + + /// 计算总体标准差,分母为 n。 + /// 值。 + public static double StdevP(this IEnumerable values) => Stdev(values, false); + + /// 计算样本标准差,分母为 n - 1。 + /// 值。 + public static double StdevS(this IEnumerable values) => Stdev(values, true); + + /// 计算标准差。 + /// 值。 + /// 计算样本标准差,分母为 n - 1。此值为 false 时计算总体标准差。 + public static float Stdev(this IEnumerable values, bool sample = false) + { + if (values == null) return 0; + + var count = 0; + var sum = 0F; + var up = 0F; + if (values is float[] array) + { + count = array.Length; + if (count < 1) return 0; + for (var i = 0; i < count; i++) sum += array[i]; + + var avg = sum / count; + for (var i = 0; i < count; i++) up += (array[i] - avg) * (array[i] - avg); + } + else if (values is IList list) + { + count = list.Count; + if (count < 1) return 0; + for (var i = 0; i < count; i++) sum += list[i]; + + var avg = sum / count; + for (var i = 0; i < count; i++) up += (list[i] - avg) * (list[i] - avg); + } + else + { + foreach (var value in values) + { + sum += value; + count++; + } + if (count < 1) return 0; + + var avg = sum / count; + foreach (var value in values) + { + up += (value - avg) * (value - avg); + count++; + } + } + + // 样本标准差,最少要 2 个值。 + if (sample) + { + var down = count - 1; + if (down == 0) return 0; + + var sigma = Convert.ToSingle(Math.Sqrt(up / down)); + return sigma; + } + else + { + var sigma = Convert.ToSingle(Math.Sqrt(up / count)); + return sigma; + } + } + + /// 计算总体标准差,分母为 n。 + /// 值。 + public static float StdevP(this IEnumerable values) => Stdev(values, false); + + /// 计算样本标准差,分母为 n - 1。 + /// 值。 + public static float StdevS(this IEnumerable values) => Stdev(values, true); + + /// 计算标准差。 + /// 值。 + /// 计算样本标准差,分母为 n - 1。此值为 false 时计算总体标准差。 + public static decimal Stdev(this IEnumerable values, bool sample = false) + { + if (values == null) return 0; + + var count = 0; + var sum = 0M; + var up = 0M; + if (values is decimal[] array) + { + count = array.Length; + if (count < 1) return 0; + for (var i = 0; i < count; i++) sum += array[i]; + + var avg = sum / count; + for (var i = 0; i < count; i++) up += (array[i] - avg) * (array[i] - avg); + } + else if (values is IList list) + { + count = list.Count; + if (count < 1) return 0; + for (var i = 0; i < count; i++) sum += list[i]; + + var avg = sum / count; + for (var i = 0; i < count; i++) up += (list[i] - avg) * (list[i] - avg); + } + else + { + foreach (var value in values) + { + sum += value; + count++; + } + if (count < 1) return 0; + + var avg = sum / count; + foreach (var value in values) + { + up += (value - avg) * (value - avg); + count++; + } + } + + // 样本标准差,最少要 2 个值。 + if (sample) + { + var down = count - 1; + if (down == 0) return 0; + + var sigma = Convert.ToDecimal(Math.Sqrt(Convert.ToDouble(up / down))); + return sigma; + } + else + { + var sigma = Convert.ToDecimal(Math.Sqrt(Convert.ToDouble(up / count))); + return sigma; + } + } + + /// 计算总体标准差,分母为 n。 + /// 值。 + public static decimal StdevP(this IEnumerable values) => Stdev(values, false); + + /// 计算样本标准差,分母为 n - 1。 + /// 值。 + public static decimal StdevS(this IEnumerable values) => Stdev(values, true); + + #endregion + + #region 最小值、最大值、极差 - private static T GetMost(Func replace, T[] values) + private static T Most(Func replace, T[] values) { if (values == null) return default; @@ -223,69 +460,56 @@ namespace Apewer return most; } - // 无法输出正确结果。 - // public static T GetMin(params IComparable[] values) - // { - // var result = GetMost((a, b) => a.CompareTo(b) == 1, values); - // return result == null ? default : (T)result; - // } - - // 无法输出正确结果。 - // public static T GetMin(params IComparable[] values) - // { - // var result = GetMost((a, b) => a.CompareTo((T)b) == 1, values); - // return result == null ? default : (T)result; - // } - - // 无法输出正确结果。 - // public static T GetMax(params IComparable[] values) - // { - // var result = GetMost((a, b) => a.CompareTo(b) == -1, values); - // return result == null ? default : (T)result; - // } - - // 无法输出正确结果。 - // public static T GetMax(params IComparable[] values) - // { - // var result = GetMost((a, b) => a.CompareTo((T)b) == -1, values); - // return result == null ? default : (T)result; - // } - /// 最小值。 - public static byte Min(params byte[] values) => GetMost((a, b) => a > b, values); + public static byte Min(params byte[] values) => Most((a, b) => a > b, values); /// 最小值。 - public static int Min(params int[] values) => GetMost((a, b) => a > b, values); + public static int Min(params int[] values) => Most((a, b) => a > b, values); /// 最小值。 - public static long Min(params long[] values) => GetMost((a, b) => a > b, values); + public static long Min(params long[] values) => Most((a, b) => a > b, values); /// 最小值。 - public static float Min(params float[] values) => GetMost((a, b) => a > b, values); + public static float Min(params float[] values) => Most((a, b) => a > b, values); /// 最小值。 - public static double Min(params double[] values) => GetMost((a, b) => a > b, values); + public static double Min(params double[] values) => Most((a, b) => a > b, values); /// 最小值。 - public static decimal Min(params decimal[] values) => GetMost((a, b) => a > b, values); + public static decimal Min(params decimal[] values) => Most((a, b) => a > b, values); /// 最大值。 - public static byte Max(params byte[] values) => GetMost((a, b) => a < b, values); + public static byte Max(params byte[] values) => Most((a, b) => a < b, values); /// 最大值。 - public static int Max(params int[] values) => GetMost((a, b) => a < b, values); + public static int Max(params int[] values) => Most((a, b) => a < b, values); /// 最大值。 - public static long Max(params long[] values) => GetMost((a, b) => a < b, values); + public static long Max(params long[] values) => Most((a, b) => a < b, values); /// 最大值。 - public static float Max(params float[] values) => GetMost((a, b) => a < b, values); + public static float Max(params float[] values) => Most((a, b) => a < b, values); /// 最大值。 - public static double Max(params double[] values) => GetMost((a, b) => a < b, values); + public static double Max(params double[] values) => Most((a, b) => a < b, values); /// 最大值。 - public static decimal Max(params decimal[] values) => GetMost((a, b) => a < b, values); + public static decimal Max(params decimal[] values) => Most((a, b) => a < b, values); + + /// 极差。 + public static int Range(params int[] values) => Max(values) - Min(values); + + /// 极差。 + public static long Range(params long[] values) => Max(values) - Min(values); + + /// 极差。 + public static float Range(params float[] values) => Max(values) - Min(values); + + /// 极差。 + public static double Range(params double[] values) => Max(values) - Min(values); + + /// 极差。 + public static decimal Range(params decimal[] values) => Max(values) - Min(values); #endregion diff --git a/Apewer/RuntimeUtility.cs b/Apewer/RuntimeUtility.cs index 088bfa5..eb24203 100644 --- a/Apewer/RuntimeUtility.cs +++ b/Apewer/RuntimeUtility.cs @@ -14,7 +14,7 @@ namespace Apewer { /// 运行时实用工具。 - public class RuntimeUtility + public static class RuntimeUtility { /// 使用默认比较器判断相等。 @@ -514,6 +514,47 @@ namespace Apewer throw new ArgumentException($"参数 {arrayType} 不是有效的数组类型。"); } + /// 是 Nullable<T> 类型。 + public static bool IsNullableType(this Type type) + { + if (type.IsGenericType && !type.IsGenericTypeDefinition) + { + Type genericTypeDefinition = type.GetGenericTypeDefinition(); + if ((object)genericTypeDefinition == typeof(Nullable<>)) + { + return true; + } + } + return false; + } + + /// 是 Nullable<T> 类型。 + public static bool IsNullableType(this Type type, out Type genericType) + { + if (type.IsGenericType && !type.IsGenericTypeDefinition) + { + Type genericTypeDefinition = type.GetGenericTypeDefinition(); + if ((object)genericTypeDefinition == typeof(Nullable<>)) + { + genericType = type.GetGenericArguments()[0]; + return true; + } + } + + genericType = null; + return false; + } + + /// 获取 Nullable<T> 实例的值。 + public static object GetNullableValue(object nullable) + { + var type = typeof(Nullable<>); + var property = type.GetProperty("Value"); + var getter = property.GetGetMethod(); + var value = getter.Invoke(nullable, null); + return value; + } + #endregion #region Collect & Dispose @@ -631,6 +672,34 @@ namespace Apewer return new Timer((x) => Invoke((Action)x, @try), action, delay > 0 ? delay : 0, -1); } + /// 尝试在主线程调用。 + /// + /// + public static void OnMainThread(Action action) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + + var context = SynchronizationContext.Current; + if (context == null) throw new InvalidOperationException("未获取到当前线程的同步上下文。"); + + context.Send(state => { action.Invoke(); }, null); + } + + /// 尝试在主线程调用。 + /// + /// + public static TResult OnMainThread(Func func) + { + if (func == null) throw new ArgumentNullException(nameof(func)); + + var context = SynchronizationContext.Current; + if (context == null) throw new InvalidOperationException("未获取到当前线程的同步上下文。"); + + var result = default(TResult); + context.Send(state => result = func.Invoke(), null); + return result; + } + #endregion #region Assembly diff --git a/Apewer/Source/SourceUtility.cs b/Apewer/Source/SourceUtility.cs index 55d2c42..f2edb49 100644 --- a/Apewer/Source/SourceUtility.cs +++ b/Apewer/Source/SourceUtility.cs @@ -599,32 +599,59 @@ namespace Apewer.Source /// 启动事务,执行指定的过程并在完成后提交事务。若过程被异常打断,则回滚事务。 /// /// - public static void InTransaction(this IDbAdo source, Action action) + public static void InTransaction(this IDbAdo source, Action action) { // 检查参数。 if (source == null) throw new ArgumentNullException(nameof(source), "数据源无效。"); - if (action == null) throw new ArgumentNullException(nameof(action), "没有指定要执行的过程。"); + if (action == null) throw new ArgumentNullException(nameof(action), "没有指定要在事物中执行的程序。"); + + InTransaction(source, () => + { + action.Invoke(); + return null; + }); + } + + /// 启动事务,执行指定的过程并在完成后提交事务。若过程被异常打断,则回滚事务。 + /// + /// + public static T InTransaction(this IDbAdo source, Func func) + { + // 检查参数。 + if (source == null) throw new ArgumentNullException(nameof(source), "数据源无效。"); + if (func == null) throw new ArgumentNullException(nameof(func), "没有指定要在事物中执行的程序。"); + + // 已经存在事务。 + if (source.Transaction != null) return func.Invoke(); // 启动事务。 var begin = source.Begin(); if (begin.NotEmpty()) throw new SqlException("无法启动事务:" + begin); + var result = default(T); + var success = false; try { // 在事务内运行。 - action.Invoke(); - - // 执行成功,提交事务。 - var commit = source.Commit(); - if (!string.IsNullOrEmpty(commit)) throw new SqlException(commit); + result = func.Invoke(); + success = true; } - catch (Exception ex) + finally { - // 执行失败,回滚事务。 - try { source.Rollback(); } catch { } - - throw ex; + if (success) + { + // 执行成功,提交事务。 + var commit = source.Commit(); + if (!string.IsNullOrEmpty(commit)) throw new SqlException(commit); + } + else + { + // 执行失败,回滚事务。 + try { source.Rollback(); } catch { } + } } + + return result; } #endregion diff --git a/Apewer/Web/ApiUtility.cs b/Apewer/Web/ApiUtility.cs index 1e9b995..41de0e8 100644 --- a/Apewer/Web/ApiUtility.cs +++ b/Apewer/Web/ApiUtility.cs @@ -824,36 +824,6 @@ namespace Apewer.Web #endregion - #region Application & Path - - /// MapPath。 - public static string MapPath(params string[] names) - { - var list = new List(); - if (names != null) - { - var invalid = StorageUtility.InvalidPathChars; - foreach (var name in names) - { - if (string.IsNullOrEmpty(name)) continue; - var split = name.Split('/', '\\'); - foreach (var s in split) - { - var sb = new StringBuilder(); - foreach (var c in s) - { - if (Array.IndexOf(invalid, "c") < 0) sb.Append(c); - } - var t = sb.ToString(); - if (!string.IsNullOrEmpty(t)) list.Add(t); - } - } - } - return list.Count > 1 ? StorageUtility.CombinePath(list.ToArray()) : ""; - } - - #endregion - #region ApiFunction Parameters diff --git a/Apewer/_Extensions.cs b/Apewer/_Extensions.cs index 7202c65..ee57e71 100644 --- a/Apewer/_Extensions.cs +++ b/Apewer/_Extensions.cs @@ -192,7 +192,7 @@ public static class Extensions public static string AntiInject(this string @this, int length = -1, string blacklist = null) => TextUtility.AntiInject(@this, length, blacklist); /// 将 Base64 字符串转换为字节数组。 - public static byte[] Base64(this string @this) => BytesUtility.FromBase64(@this); + public static byte[] Base64(this string text) => BytesUtility.FromBase64(text); /// 对 URL 编码。 public static string EncodeUrl(this string @this) => UrlEncoding.Encode(@this); @@ -223,36 +223,12 @@ public static class Extensions #region Byte[] - /// 将字节数组格式化为十六进制字符串,默认为大写。例:D41D8CD98F00B204E9800998ECF8427E - public static string X2(this byte[] @this, bool upperCase = true) => BytesUtility.ToX2(upperCase, @this); - - /// 克隆字节数组。当源为 NULL 时,将获取零元素字节数组。 - public static byte[] Clone(this byte[] @this) => BytesUtility.Clone(@this); - - /// 确定此字节数组实例的开头是否与指定的字节数组匹配。 - public static bool Starts(this byte[] @this, params byte[] head) => BytesUtility.StartsWith(@this, head); - - /// 确定此字节数组实例的结尾是否与指定的字节数组匹配。 - public static bool Ends(this byte[] @this, params byte[] head) => BytesUtility.EndsWith(@this, head); - /// 将字节数组转换为 Base64 字符串。 public static string Base64(this byte[] @this) => BytesUtility.ToBase64(@this); - /// 获取 MD5 值。 - public static byte[] MD5(params byte[] @this) => BytesUtility.MD5(@this); - - /// 获取 SHA1 值。 - public static byte[] SHA1(params byte[] @this) => BytesUtility.SHA1(@this); - - /// 获取 SHA256 值。 - public static byte[] SHA256(params byte[] @this) => BytesUtility.SHA256(@this); - /// 将字节数组转换为文本,默认为 UTF-8。 public static string Text(this byte[] @this, Encoding encoding = null) => TextUtility.FromBytes(@this, encoding); - /// 为字节数组增加字节。 - public static byte[] Append(this byte[] @this, params byte[] bytes) => BytesUtility.Merge(@this, bytes); - /// 检查字节数组是 UTF-8 文本,默认最多检测 1MB 数据(指定为 0 将不限制检查长度)。 public static bool IsUTF8(this byte[] @this, int checkLength = 1048576) => TextUtility.IsUTF8(@this, checkLength, null); diff --git a/ChangeLog.md b/ChangeLog.md index 8ebbefe..c780d3e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,19 @@  ### 最新提交 +### 6.7.6 +- 新特性 + - CollectionUtility:增加数组的 Push 和 Unshift 方法; + - Json:支持序号索引访问 Json 数组; + - Json:静态属性 Json.DeserializeDateTime 可自定义 DateTime 的反序列化,允许用户代码转换字符串或 DateTime.Kind; + - NumberUtility:增加平均值、标准差、极差; + - Source:InTransaction 支持使用已存在的事务; + - Windows:增加扩展方法 AllowResizeForm,支持鼠标调整无边框窗体的大小。 +- 问题修正 + - ByetsUtility:修正 ToBase64 的 null 引用问题; + - Source:InTransaction(Action) 去除无用的泛型要求; + - Source:InTransaction 不再捕获异常,直接抛出 action 的异常。 + ### 6.7.5 - 问题修正 - Query:删除了一些访问单元格的重载方法,减少歧义;