diff --git a/binding/csharp/CHANGELOG.md b/binding/csharp/CHANGELOG.md
index 7dd8005..473b68d 100644
--- a/binding/csharp/CHANGELOG.md
+++ b/binding/csharp/CHANGELOG.md
@@ -2,7 +2,10 @@
All notable changes to this project will be documented in this file.
-## [Unreleased]
+## [3.0.0] - 2025-11-22
+- 支持 .NET 10.0
+- 增加 IPv6 支持
+- 修复若干 bug
## [2.0.1] - 2023-07-30
@@ -18,4 +21,4 @@ All notable changes to this project will be documented in this file.
### Added
- Dependent file query policies CachePolicy.VectorIndex, CachePolicy.File support thread-safe concurrent queries
-- Dramatically optimizes overall performance
\ No newline at end of file
+- Dramatically optimizes overall performance
diff --git a/binding/csharp/IP2Region.Net.BenchMark/Benmarks.cs b/binding/csharp/IP2Region.Net.BenchMark/Benmarks.cs
index eabf368..c3a23e7 100644
--- a/binding/csharp/IP2Region.Net.BenchMark/Benmarks.cs
+++ b/binding/csharp/IP2Region.Net.BenchMark/Benmarks.cs
@@ -10,6 +10,7 @@ using IP2Region.Net.XDB;
namespace IP2Region.Net.BenchMark;
+[MemoryDiagnoser]
public class Benchmarks
{
private static readonly string XdbPathV4 = Path.Combine(AppContext.BaseDirectory, "IP2Region", "ip2region_v4.xdb");
@@ -24,6 +25,17 @@ public class Benchmarks
private readonly string _testIPv4Address = "114.114.114.114";
private readonly string _testIPv6Address = "240e:3b7:3272:d8d0:db09:c067:8d59:539e";
+ public Benchmarks()
+ {
+ _contentV4Searcher.Search(_testIPv4Address);
+ _vectorV4Searcher.Search(_testIPv4Address);
+ _fileV4Searcher.Search(_testIPv4Address);
+
+ _contentV6Searcher.Search(_testIPv6Address);
+ _vectorV6Searcher.Search(_testIPv6Address);
+ _fileV6Searcher.Search(_testIPv6Address);
+ }
+
[Benchmark]
[BenchmarkCategory("IPv4")]
public void ContentIPv4() => _contentV4Searcher.Search(_testIPv4Address);
diff --git a/binding/csharp/IP2Region.Net.Test/SearcherTest.cs b/binding/csharp/IP2Region.Net.Test/SearcherTest.cs
index da9f840..46ba960 100644
--- a/binding/csharp/IP2Region.Net.Test/SearcherTest.cs
+++ b/binding/csharp/IP2Region.Net.Test/SearcherTest.cs
@@ -78,6 +78,8 @@ public class SearcherTest
searcher.Search("58.251.27.201");
Assert.Equal(3, searcher.IoCount);
+
+ searcher.Dispose();
}
[Fact]
@@ -89,6 +91,8 @@ public class SearcherTest
searcher.Search("58.251.27.201");
Assert.Equal(2, searcher.IoCount);
+
+ searcher.Dispose();
}
[Fact]
@@ -100,6 +104,8 @@ public class SearcherTest
searcher.Search("58.251.27.201");
Assert.Equal(0, searcher.IoCount);
+
+ searcher.Dispose();
}
[Theory]
@@ -110,10 +116,6 @@ public class SearcherTest
var ipAddress = IPAddress.Parse(ipStr);
var region = fileSearcher.Search(ipAddress);
Assert.Equal(expected, region);
-
- var ip = XDB.Util.IpAddressToUInt32(ipAddress);
- region = fileSearcher.Search(ip);
- Assert.Equal(expected, region);
}
[Theory]
diff --git a/binding/csharp/IP2Region.Net/Abstractions/ISearcher.cs b/binding/csharp/IP2Region.Net/Abstractions/ISearcher.cs
index 89cd5e5..b8cf052 100644
--- a/binding/csharp/IP2Region.Net/Abstractions/ISearcher.cs
+++ b/binding/csharp/IP2Region.Net/Abstractions/ISearcher.cs
@@ -12,7 +12,7 @@ namespace IP2Region.Net.Abstractions;
///
/// IP 转化为地理位置搜索器接口
///
-public interface ISearcher
+public interface ISearcher : IDisposable
{
///
/// 搜索方法
@@ -30,6 +30,7 @@ public interface ISearcher
/// 搜索方法 仅限 IPv4 使用
///
/// IPv4 地址字节数组小端读取 uint 数值
+ [Obsolete("已弃用,请改用其他方法;Deprecated; please use Search(string) or Search(IPAddress) method.")]
string? Search(uint ipAddress);
///
diff --git a/binding/csharp/IP2Region.Net/IP2Region.Net.csproj b/binding/csharp/IP2Region.Net/IP2Region.Net.csproj
index 83901fe..4f693d6 100644
--- a/binding/csharp/IP2Region.Net/IP2Region.Net.csproj
+++ b/binding/csharp/IP2Region.Net/IP2Region.Net.csproj
@@ -2,9 +2,9 @@
IP2Region.Net
- 2.1.1
+ 3.0.0
IP2Region.Net
- Alan Lee
+ Alan Lee;Argo Zhang(argo@live.ca)
Apache-2.0
README.md
https://github.com/lionsoul2014/ip2region/tree/master/binding/csharp
diff --git a/binding/csharp/IP2Region.Net/Internal/Abstractions/AbstractCacheStrategy.cs b/binding/csharp/IP2Region.Net/Internal/Abstractions/AbstractCacheStrategy.cs
deleted file mode 100644
index 909f75c..0000000
--- a/binding/csharp/IP2Region.Net/Internal/Abstractions/AbstractCacheStrategy.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2025 The Ip2Region Authors. All rights reserved.
-// Use of this source code is governed by a Apache2.0-style
-// license that can be found in the LICENSE file.
-// @Author Alan
-// @Date 2023/07/25
-// Updated by Argo Zhang at 2025/11/21
-
-using System.Buffers;
-
-namespace IP2Region.Net.Internal.Abstractions;
-
-internal abstract class AbstractCacheStrategy(string xdbPath)
-{
- protected const int HeaderInfoLength = 256;
- protected const int VectorIndexSize = 8;
-
- private const int BufferSize = 64 * 1024;
-
- public int IoCount { get; private set; }
-
- protected FileStream XdbFileStream = new(xdbPath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, FileOptions.RandomAccess);
-
- public void ResetIoCount()
- {
- IoCount = 0;
- }
-
- public virtual ReadOnlyMemory GetVectorIndex(int offset) => GetData(HeaderInfoLength + offset, VectorIndexSize);
-
- public virtual ReadOnlyMemory GetData(long offset, int length)
- {
- byte[] buffer = ArrayPool.Shared.Rent(length);
- int totalBytesRead = 0;
- try
- {
- XdbFileStream.Seek(offset, SeekOrigin.Begin);
-
- int bytesRead;
- while (totalBytesRead < length)
- {
- bytesRead = XdbFileStream.Read(buffer, totalBytesRead, length);
- totalBytesRead += bytesRead;
-
- IoCount++;
- }
- }
- finally
- {
- ArrayPool.Shared.Return(buffer);
- }
-
- return new ReadOnlyMemory(buffer, 0, totalBytesRead);
- }
-}
diff --git a/binding/csharp/IP2Region.Net/Internal/CacheStrategyFactory.cs b/binding/csharp/IP2Region.Net/Internal/CacheStrategyFactory.cs
index 084a3d0..ae9b01d 100644
--- a/binding/csharp/IP2Region.Net/Internal/CacheStrategyFactory.cs
+++ b/binding/csharp/IP2Region.Net/Internal/CacheStrategyFactory.cs
@@ -5,14 +5,13 @@
// @Date 2023/07/25
// Updated by Argo Zhang at 2025/11/21
-using IP2Region.Net.Internal.Abstractions;
using IP2Region.Net.XDB;
namespace IP2Region.Net.Internal;
-class CacheStrategyFactory(string xdbPath)
+static class CacheStrategyFactory
{
- public AbstractCacheStrategy CreateCacheStrategy(CachePolicy cachePolicy) => cachePolicy switch
+ public static ICacheStrategy CreateCacheStrategy(CachePolicy cachePolicy, string xdbPath) => cachePolicy switch
{
CachePolicy.Content => new ContentCacheStrategy(xdbPath),
CachePolicy.VectorIndex => new VectorIndexCacheStrategy(xdbPath),
diff --git a/binding/csharp/IP2Region.Net/Internal/ContentCacheStrategy.cs b/binding/csharp/IP2Region.Net/Internal/ContentCacheStrategy.cs
index 135751d..020a959 100644
--- a/binding/csharp/IP2Region.Net/Internal/ContentCacheStrategy.cs
+++ b/binding/csharp/IP2Region.Net/Internal/ContentCacheStrategy.cs
@@ -5,11 +5,9 @@
// @Date 2023/07/25
// Updated by Argo Zhang at 2025/11/21
-using IP2Region.Net.Internal.Abstractions;
-
namespace IP2Region.Net.Internal;
-class ContentCacheStrategy : AbstractCacheStrategy
+class ContentCacheStrategy : FileCacheStrategy
{
private readonly ReadOnlyMemory _cacheData;
@@ -20,5 +18,10 @@ class ContentCacheStrategy : AbstractCacheStrategy
XdbFileStream.Dispose();
}
- public override ReadOnlyMemory GetData(long offset = 0, int length = 0) => _cacheData.Slice((int)offset, length);
+ public override ReadOnlyMemory GetData(long offset, int length) => _cacheData.Slice((int)offset, length);
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(false);
+ }
}
diff --git a/binding/csharp/IP2Region.Net/Internal/FileCacheStrategy.cs b/binding/csharp/IP2Region.Net/Internal/FileCacheStrategy.cs
index 2a69364..00310e1 100644
--- a/binding/csharp/IP2Region.Net/Internal/FileCacheStrategy.cs
+++ b/binding/csharp/IP2Region.Net/Internal/FileCacheStrategy.cs
@@ -5,11 +5,81 @@
// @Date 2023/07/25
// Updated by Argo Zhang at 2025/11/21
-using IP2Region.Net.Internal.Abstractions;
+using System.Buffers;
namespace IP2Region.Net.Internal;
-class FileCacheStrategy(string xdbPath) : AbstractCacheStrategy(xdbPath)
+class FileCacheStrategy(string xdbPath) : ICacheStrategy
{
+ protected const int HeaderInfoLength = 256;
+ protected const int VectorIndexSize = 8;
+ protected const int BufferSize = 64 * 1024;
+
+ protected FileStream XdbFileStream = new(xdbPath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, FileOptions.RandomAccess);
+
+ public int IoCount { get; set; }
+
+ public void ResetIoCount()
+ {
+ IoCount = 0;
+ }
+
+ public virtual ReadOnlyMemory GetVectorIndex(int offset) => GetData(HeaderInfoLength + offset, VectorIndexSize);
+
+ public virtual ReadOnlyMemory GetData(long offset, int length)
+ {
+ var buffer = ArrayPool.Shared.Rent(length);
+ try
+ {
+ int totalBytesRead = 0;
+ XdbFileStream.Seek(offset, SeekOrigin.Begin);
+
+ int bytesRead;
+ while (totalBytesRead < length)
+ {
+ bytesRead = XdbFileStream.Read(buffer, totalBytesRead, length - totalBytesRead);
+ if (bytesRead == 0)
+ {
+ break;
+ }
+
+ totalBytesRead += bytesRead;
+ IoCount++;
+ }
+
+ var ret = new byte[totalBytesRead];
+ if (totalBytesRead > 0)
+ {
+ Array.Copy(buffer, 0, ret, 0, totalBytesRead);
+ }
+ return ret;
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
+ }
+ }
+
+ ///
+ /// 释放文件句柄
+ ///
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ XdbFileStream.Close();
+ XdbFileStream.Dispose();
+ }
+ }
+
+ ///
+ ///
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
}
diff --git a/binding/csharp/IP2Region.Net/Internal/ICacheStrategy.cs b/binding/csharp/IP2Region.Net/Internal/ICacheStrategy.cs
new file mode 100644
index 0000000..3d05b31
--- /dev/null
+++ b/binding/csharp/IP2Region.Net/Internal/ICacheStrategy.cs
@@ -0,0 +1,19 @@
+// Copyright 2025 The Ip2Region Authors. All rights reserved.
+// Use of this source code is governed by a Apache2.0-style
+// license that can be found in the LICENSE file.
+// @Author Alan
+// @Date 2023/07/25
+// Updated by Argo Zhang at 2025/11/21
+
+namespace IP2Region.Net.Internal;
+
+internal interface ICacheStrategy : IDisposable
+{
+ int IoCount { get; }
+
+ void ResetIoCount();
+
+ ReadOnlyMemory GetVectorIndex(int offset);
+
+ ReadOnlyMemory GetData(long offset, int length);
+}
diff --git a/binding/csharp/IP2Region.Net/Internal/VectorIndexCacheStrategy.cs b/binding/csharp/IP2Region.Net/Internal/VectorIndexCacheStrategy.cs
index b5abc17..c7bdaf1 100644
--- a/binding/csharp/IP2Region.Net/Internal/VectorIndexCacheStrategy.cs
+++ b/binding/csharp/IP2Region.Net/Internal/VectorIndexCacheStrategy.cs
@@ -5,11 +5,9 @@
// @Date 2023/07/25
// Updated by Argo Zhang at 2025/11/21
-using IP2Region.Net.Internal.Abstractions;
-
namespace IP2Region.Net.Internal;
-class VectorIndexCacheStrategy : AbstractCacheStrategy
+class VectorIndexCacheStrategy : FileCacheStrategy
{
private const int VectorIndexRows = 256;
private const int VectorIndexCols = 256;
@@ -18,10 +16,7 @@ class VectorIndexCacheStrategy : AbstractCacheStrategy
public VectorIndexCacheStrategy(string xdbPath) : base(xdbPath)
{
- XdbFileStream.Seek(HeaderInfoLength, SeekOrigin.Begin);
- var buffer = new byte[VectorIndexRows * VectorIndexCols * VectorIndexSize];
- var length = XdbFileStream.Read(buffer, 0, buffer.Length);
- _vectorCache = new ReadOnlyMemory(buffer);
+ _vectorCache = GetData(HeaderInfoLength, VectorIndexRows * VectorIndexCols * VectorIndexSize);
}
public override ReadOnlyMemory GetVectorIndex(int offset) => _vectorCache.Slice(offset, VectorIndexSize);
diff --git a/binding/csharp/IP2Region.Net/XDB/Searcher.cs b/binding/csharp/IP2Region.Net/XDB/Searcher.cs
index 2f0eadb..abccb75 100644
--- a/binding/csharp/IP2Region.Net/XDB/Searcher.cs
+++ b/binding/csharp/IP2Region.Net/XDB/Searcher.cs
@@ -7,8 +7,8 @@
using IP2Region.Net.Abstractions;
using IP2Region.Net.Internal;
-using IP2Region.Net.Internal.Abstractions;
using System.Buffers.Binary;
+using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Text;
@@ -17,24 +17,18 @@ namespace IP2Region.Net.XDB;
///
/// 实现类
///
-public class Searcher : ISearcher
+///
+///
+///
+public class Searcher(CachePolicy cachePolicy, string xdbPath) : ISearcher
{
- private readonly AbstractCacheStrategy _cacheStrategy;
+ private readonly ICacheStrategy _cacheStrategy = CacheStrategyFactory.CreateCacheStrategy(cachePolicy, xdbPath);
///
///
///
public int IoCount => _cacheStrategy.IoCount;
- ///
- ///
- ///
- public Searcher(CachePolicy cachePolicy, string dbPath)
- {
- var factory = new CacheStrategyFactory(dbPath);
- _cacheStrategy = factory.CreateCacheStrategy(cachePolicy);
- }
-
///
///
///
@@ -47,14 +41,13 @@ public class Searcher : ISearcher
///
///
///
- public string? Search(IPAddress ipAddress)
- {
- return SearchCore(ipAddress.GetAddressBytes());
- }
+ public string? Search(IPAddress ipAddress) => SearchCore(ipAddress.GetAddressBytes());
///
///
///
+ [Obsolete("已弃用,请改用其他方法;Deprecated; please use Search(string) or Search(IPAddress) method.")]
+ [ExcludeFromCodeCoverage]
public string? Search(uint ipAddress)
{
var bytes = BitConverter.GetBytes(ipAddress);
@@ -93,7 +86,7 @@ public class Searcher : ISearcher
{
int m = (int)(l + h) >> 1;
- var p = (int)sPtr + m * indexSize;
+ var p = sPtr + m * indexSize;
var buff = _cacheStrategy.GetData(p, indexSize);
var s = buff.Span.Slice(0, length);
@@ -114,7 +107,7 @@ public class Searcher : ISearcher
}
}
- var regionBuff = _cacheStrategy.GetData((int)dataPtr, dataLen);
+ var regionBuff = _cacheStrategy.GetData(dataPtr, dataLen);
return Encoding.UTF8.GetString(regionBuff.Span.ToArray());
}
@@ -154,4 +147,13 @@ public class Searcher : ISearcher
}
return ret;
}
+
+ ///
+ ///
+ ///
+ public void Dispose()
+ {
+ _cacheStrategy.Dispose();
+ GC.SuppressFinalize(this);
+ }
}
diff --git a/binding/csharp/README.md b/binding/csharp/README.md
index 5f1db40..ab3c1b4 100644
--- a/binding/csharp/README.md
+++ b/binding/csharp/README.md
@@ -24,6 +24,7 @@ ISearcher searcher = new Searcher(CachePolicy , "your xdb file path");
| CachePolicy.Content | Cache the entire `xdb` data. | Yes |
| CachePolicy.VectorIndex | Cache `vecotorIndex` to speed up queries and reduce system io pressure by reducing one fixed IO operation. | Yes |
| CachePolicy.File | Completely file-based queries | Yes |
+
### XDB File Description
Generate using [maker](https://github.com/lionsoul2014/ip2region/tree/master/maker/csharp), or [download](https://github.com/lionsoul2014/ip2region/blob/master/data/ip2region.xdb) pre-generated xdb files
@@ -47,15 +48,47 @@ provider.GetRequiredKeyedService("IP2Region.Net");
netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0;net10.0
## Performance
+// * Summary *
-| Method | Mean | Error | StdDev | Median |
-|------------ |------------:|----------:|------------:|------------:|
-| ContentIPv4 | 101.5 ns | 2.04 ns | 4.57 ns | 103.1 ns |
-| VectorIPv4 | 7,488.1 ns | 222.91 ns | 657.26 ns | 7,819.2 ns |
-| FileIPv4 | 11,686.9 ns | 59.81 ns | 55.95 ns | 11,707.6 ns |
-| ContentIPv6 | 296.1 ns | 1.84 ns | 1.72 ns | 296.2 ns |
-| VectorIPv6 | 15,025.1 ns | 938.17 ns | 2,766.21 ns | 16,642.9 ns |
-| FileIPv6 | 19,721.0 ns | 807.55 ns | 2,381.08 ns | 20,905.7 ns |
+BenchmarkDotNet v0.15.6, Windows 11 (10.0.26200.7171)
+13th Gen Intel Core i7-13700 2.10GHz, 1 CPU, 24 logical and 16 physical cores
+.NET SDK 10.0.100
+ [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+ DefaultJob : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+
+
+| Method | Mean | Error | StdDev | Gen0 | Allocated |
+|------------ |-------------:|-----------:|-----------:|-------:|----------:|
+| ContentIPv4 | 53.70 ns | 0.296 ns | 0.277 ns | 0.0086 | 136 B |
+| VectorIPv4 | 4,446.04 ns | 18.673 ns | 15.593 ns | 0.0076 | 232 B |
+| FileIPv4 | 6,712.40 ns | 15.718 ns | 13.934 ns | 0.0153 | 264 B |
+| ContentIPv6 | 145.53 ns | 0.331 ns | 0.277 ns | 0.0126 | 200 B |
+| VectorIPv6 | 7,058.39 ns | 125.505 ns | 117.398 ns | 0.0381 | 712 B |
+| FileIPv6 | 10,657.97 ns | 53.907 ns | 50.425 ns | 0.0458 | 744 B |
+
+// * Hints *
+Outliers
+ Benchmarks.VectorIPv4: Default -> 2 outliers were removed (4.55 us, 4.58 us)
+ Benchmarks.FileIPv4: Default -> 1 outlier was removed (6.79 us)
+ Benchmarks.ContentIPv6: Default -> 2 outliers were removed (148.08 ns, 152.27 ns)
+
+// * Legends *
+ Mean : Arithmetic mean of all measurements
+ Error : Half of 99.9% confidence interval
+ StdDev : Standard deviation of all measurements
+ Gen0 : GC Generation 0 collects per 1000 operations
+ Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
+ 1 ns : 1 Nanosecond (0.000000001 sec)
+
+// * Diagnostic Output - MemoryDiagnoser *
+
+
+// ***** BenchmarkRunner: End *****
+Run time: 00:02:06 (126.09 sec), executed benchmarks: 6
+
+Global total time: 00:02:13 (133.47 sec), executed benchmarks: 6
+// * Artifacts cleanup *
+Artifacts cleanup is finished
## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.