65.9K
CodeProject 正在变化。 阅读更多。
Home

11 个你会喜欢的实用类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (22投票s)

2011 年 6 月 18 日

Ms-PL

6分钟阅读

viewsIcon

44551

downloadIcon

1246

模拟 Windows 用户(以及可能遇到的“有趣”的 bug)、正确释放 WCF CommunicationObject、从表达式获取属性名、获取相对路径、弱引用集合监听器、线程安全字典、枚举标志帮助、测试助手

引言

这些工具类是我近年来从互联网上收集的代码片段。我相信你在这里会找到日常使用的类。

我的目标不是创建一个库,而是将很酷的代码片段聚集在一个地方,供人们通过复制粘贴使用,而且没有任何依赖项。

如果还没这样做,我强烈建议你收集你在网上看到的很酷的代码片段,并将你的个人框架保存在代码存储库中。这会为你节省大量时间。

你们中的一些人会说不必重复造轮子,这是对的。

但关键是,有时你需要处理非常具体且简单(但不容易)的任务,而又不想承担另一个库的负担。而且,你自己最有可能想要在以后重用这些类:你知道在哪里找到它们,它们如何工作,有什么陷阱,以及你在使用它们时学到了什么。

我保留了代码中的原始注释,所以有时你或许能找到原始作者。

我想感谢那些在网上分享这些类的人,他们为我节省了大量的时间和金钱。我能做的最好的事情就是传播他们的工作。

这里展示的所有单元测试或代码片段都能通过。

目录

11 个实用类

LogonUser:如何轻松模拟用户

有时,你需要以另一个用户的 Windows 凭据运行一段代码,以使用他的 ACL 权限。这称为模拟。这里有一个例子。

[TestMethod]
public void CanUndoImpersonation()
{
	var oldName = Impersonation.CurrentUserName;
	using(Impersonation.Impersonate(TestUser.UserName, TestUser.Password))
	{
		AssertEx.ShouldContains
			(TestUser.UserName, Impersonation.CurrentUserName);
		AssertEx.ShouldNotContains(TestUser.UserName, oldName);
	}
	AssertEx.ShouldContains(oldName, Impersonation.CurrentUserName);
}

其中 Impersonation.CurrentUserName

public static String CurrentUserName
{
	get
	{
		return System.Security.Principal.WindowsIdentity.GetCurrent().Name;
	}
}

需要注意的陷阱

我通过惨痛教训学到的,需要注意的一些“有趣”的 bug...

[TestMethod]
public void LazyAssemblyInAImpersonatedSecurityContextWithoutFileRightsThrows()
{
	using(Impersonation.Impersonate(TestUser.UserName, TestUser.Password))
	{
		AssertEx.Throws<FileLoadException>(() =>
		{
			Class1 hello = new Class1();
		});
	}
}

在这段代码中,代码抛出异常是因为 Class1(位于另一个程序集中)在 AssertEx.Throws 中的 lambda 表达式被“即时编译”(jit)时被加载。而这发生在使用 TestUser 的凭据下,而该用户没有读取程序集文件的权限!

这段代码运行正常

[TestMethod]
public void LoadAssemblyBeforeImpersonatingSoYouDontLazyLoadInTheWrongSecurityContext()
{
	Class1 test = null;
	using(Impersonation.Impersonate(TestUser.UserName, TestUser.Password))
	{
		AssertEx.DoesntThrow(() =>
		{
			Class1 hello = new Class1();
		});
	}
}

但还有一个惊喜等着你,我怀疑这是 .NET Framework 的一个 bug(不确定是否是设计如此)。

如果你尝试在一个用户没有读取权限的情况下加载一个程序集,那么以后你就永远无法加载它了。

[TestMethod]
public void AndYouCantEvenRetryToLoadAssemblyInTheRightContext()
{
	var oldAccount = Impersonation.CurrentUserName;
	using(Impersonation.Impersonate(TestUser.UserName, TestUser.Password))
	{
		AssertEx.Throws<FileLoadException>(() =>
		{
			Class1 hello = new Class1();
		});
	}
	Assert.AreEqual(oldAccount, Impersonation.CurrentUserName);
	AssertEx.Throws<FileLoadException>(() =>
	{
		Class1 hello = new Class1();
	});
}

当我看到我之前的两个测试同时运行时都无法通过时,我发现了这个问题...

PathUtil:如何从绝对文件夹获取相对路径

这个任务非常简单... 但不容易编写!下面是如何使用它。

[TestMethod]
public void CanGetRelativePath()
{
	var path = PathUtil.RelativePathTo(@"c:\toto\titi", @"c:\toto\titi\tutu.txt");
	Assert.AreEqual("tutu.txt", path);
	path = PathUtil.RelativePathTo(@"c:\toto\tata", @"c:\toto\titi\tutu.txt");
	Assert.AreEqual(@"..\titi\tutu.txt", path);
}

是不是很酷?但要注意在路径中使用 "\" 而不是 "/"... 如果你想修复它,代码就在那里。

LambdaExtensions:从表达式提取属性名的扩展方法

这个是关于避免在代码中使用魔术字符串(magic string)来表示属性名,这样你在重构后就不会出现愚蠢的错误。

[TestMethod]
public void CanGetPropertyName()
{
	TestClass test = new TestClass();
	var propertyName = LambdaExtensions.GetPropertyName(() => test.ReferenceMember);
	Assert.AreEqual("ReferenceMember", propertyName);
	propertyName = LambdaExtensions.GetPropertyName(() => test.ValueMember);
	Assert.AreEqual("ValueMember", propertyName);
}

NotifyPropertyChangedBase:LambdaExtensions 的具体应用场景

如果你正在进行 WPF 或 Silverlight 开发,我相信你很欣赏如何用代码片段声明 ViewModel 的一个属性

public class ViewModel : NotifyPropertyChangedBase
{
	private int _Value;
	public int Value
	{
		get
		{
			return _Value;
		}
		set
		{
			if(value != _Value)
			{
				_Value = value;
				OnPropertyChanged(() => this.Value);
			}
		}
	}
}

这是确保它按预期正常工作的测试

[TestMethod]
public void TypedOnPropertyChangedFireCorrectly()
{
	ViewModel vm = new ViewModel();
	bool fired = false;
	vm.PropertyChanged += (s, a) =>
	{
		Assert.AreEqual("Value", a.PropertyName);
		fired = true;
	};
	vm.Value = 5;
	Assert.IsTrue(fired);
}

不满意?你想想如何轻松地在另一个依赖属性上触发 PropertyChanged 事件?看看构造函数!

public class ViewModel : NotifyPropertyChangedBase
{
	public ViewModel()
	{
		this.AddDependency(() => this.Value, () => this.CalculatedValue);
	}
	private int _Value;
	public int Value
	{
		get
		{
			return _Value;
		}
		set
		{
			if(value != _Value)
			{
				_Value = value;
				OnPropertyChanged(() => this.Value);
			}
		}
	}

	public int CalculatedValue
	{
		get
		{
			return Value + 5;
		}
	}
}

还有这个测试

[TestMethod]
public void DependantPropertyFireCorrectly()
{
	ViewModel vm = new ViewModel();
	bool fired = false;
	vm.PropertyChanged += (s, a) =>
	{
		if(a.PropertyName == "CalculatedValue")
			fired = true;
	};
	vm.Value = 5;
	Assert.IsTrue(fired);
}

DisposableWrapperExtension:如何有效地释放 WCF CommunicationObject

你说你足够了解如何使用 IDisposable 接口?那么你怎么看这个。

[TestMethod]
public void WCFDisposableProblem()
{
	ServiceHost host = new ServiceHost(new TestService());
	var endpoint = host.AddServiceEndpoint(typeof(ITestService), 
			new WSHttpBinding(), "https://:8372/");
	host.Open();
	var client = new ChannelFactory<ITestService>(endpoint).CreateChannel();
	bool throwAtTheEndOfUsing = false;
	AssertEx.Throws<EndpointNotFoundException>(() =>
	{
		using(((IClientChannel)client))
		{
			client.DoStuff();
			host.Close();
			throwAtTheEndOfUsing = true;
		}
	});
	Assert.IsTrue(throwAtTheEndOfUsing);
}

这里我们在客户端通道之前关闭了 ServiceHost,所以客户端通道在 Dispose 方法中抛出异常,而不是简单地中止...

我发誓,当你第一次发现这个行为时,你只想停止编程,然后去乡下种土豆,度过余生...

所以为了保持你作为开发者的尊严和热爱,请使用 DisposableWrapperExtension,方法如下,感谢一位 Stack Overflow 的大神

[TestMethod]
public void CanUseFixDisposable()
{
	ServiceHost host = new ServiceHost(new TestService());
	var endpoint = host.AddServiceEndpoint(typeof(ITestService), 
			new WSHttpBinding(), "https://:8374/");
	host.Open();
	var client = new ChannelFactory<ITestService>(endpoint).CreateChannel();
	using(((IClientChannel)client).FixIDisposable())
	{
		client.DoStuff();
		host.Close();
	}
}

它在 ThreadPool 的线程中 Dispose,然后 Abort 通道,这样异常就不会导致你的 AppDomain 崩溃。(这也是我血泪教训学到的...)

EnumExtensions:如何轻松操作标志枚举

如果布尔运算不适合你,那么这里有一个简单的类来操作标志 enum,例如,使用这个 enum

[Flags]
public enum ErrorTypes
{
	None = 0,
	MissingPassword = 1,
	MissingUsername = 2,
	PasswordIncorrect = 4
}

你可以轻松地看到哪个标志是开启或关闭的

[TestMethod]
public void CanAppendRemoveAndConsultFlagEnum()
{
	ErrorTypes error = ErrorTypes.MissingPassword;
	Assert.IsTrue(error.Has(ErrorTypes.MissingPassword));
	error = error.Append(ErrorTypes.MissingUsername);
	Assert.IsTrue(error.Has(ErrorTypes.MissingPassword));
	Assert.IsTrue(error.Has(ErrorTypes.MissingUsername));
	Assert.IsTrue(error.Has(ErrorTypes.MissingPassword.Append
				(ErrorTypes.MissingUsername)));
	error = error.Remove(ErrorTypes.MissingPassword);
	Assert.IsFalse(error.Has(ErrorTypes.MissingPassword));
	Assert.IsTrue(error.Has(ErrorTypes.MissingUsername));
}

ThreadSafeDictionary:如何自动添加项目,轻松实现

一个线程安全的 Dictionary,你可以自动添加/替换或删除项目(无需先调用 ContainsKey)。

[TestMethod]
public void CanDeleteAndRemoveItems()
{
	ThreadSafeDictionary<int, string> dictionary = 
				new ThreadSafeDictionary<int, string>();
	dictionary.MergeSafe(1, "hello");
	dictionary.MergeSafe(2, "hello2");
	Assert.AreEqual(2, dictionary.Count);
	dictionary.RemoveSafe(3);
	dictionary.RemoveSafe(2);
	Assert.AreEqual(1, dictionary.Count);
	Assert.IsTrue(dictionary.ContainsKey(1));
}

该字典可以在多个线程之间安全共享。

BlockingStream:如何轻松模拟客户端/服务器通信,仅使用一个 Stream

这个类,来自 StackOverflow,将为你节省单元测试的时间... 特别是当你想要模拟客户端/服务器(生产者/消费者)交互,而又不需要依赖 Socket、WCF、System.Net 或 Remoting 时。

[TestMethod]
public void CanWriteThenRead()
{
	BlockingStream stream = new BlockingStream();
	stream.WriteByte(5);
	var value = stream.ReadByte();
	Assert.AreEqual(5, value);
}
[TestMethod]
public void CanTimeout()
{
	BlockingStream stream = new BlockingStream();
	stream.ReadTimeout = 500;
	Stopwatch watcher = new Stopwatch();
	watcher.Start();
	var value = stream.ReadByte();
	watcher.Stop();
	Assert.IsTrue(500 < watcher.ElapsedMilliseconds);
	Assert.AreEqual(-1, value);
}

WeakCollectionChangedListener:如何停止内存泄漏,当监听器的生命周期比被监听对象短时。

SpyObseravableCollection 是一个实现了 INotifyCollectionChanged 的类,用于使我的测试更容易阅读。

[TestMethod]
public void ListenerCanBeGarbageCollected()
{
	var listener = new SpyCollectionListener();
	var source = new SpyObservableCollection();
	WeakCollectionChangedListener weakListener = 
			new WeakCollectionChangedListener(source, listener);
	source.Add(4);
	listener.AssertCollectionChanged();
	WeakReference listenerHandle = new WeakReference(listener);
	listener = null;
	GC.Collect();
	Assert.IsFalse(listenerHandle.IsAlive);
	source.Add(6); //The weak listener unsubscribe on the next event
	source.AssertHasNoListener();
}

WeakObservableCollection:一个具体的应用场景,被监听对象的包装器,由监听器拥有

WeakCollectionChangedListener 并不难使用,但我有另一个更易读的解决方案。

我只是将源集合包装在 WeakObservableCollection 中。当 WeakObservableCollection 被垃圾回收时,它所有的监听器都会从源集合中取消订阅。

[TestMethod]
public void 
  ListenerOfWeakObservableCollectionCanBeGarbageCollectedWhenWeakCollectionCollected()
{
	var source = new SpyObservableCollection();
	var wrappedSource = new WeakObservableCollection<int>(source);
	var listener = new SpyCollectionListener();
	wrappedSource.CollectionChanged += listener.OnCollectionChanged;
	source.Add(4);
	listener.AssertCollectionChanged();
	WeakReference listenerHandle = new WeakReference(listener);
	listener = null;
	wrappedSource = null;
	GC.Collect();
	Assert.IsFalse(listenerHandle.IsAlive);
	source.Add(6); //The weak listener unsubscribe on the next event
	source.AssertHasNoListener();
}

AssertEx:一个让你的测试更具可读性的类

我在文章中使用了这个类,抱歉关于这个,我太懒了,不想写关于测试的测试(元测试!)所以我只是提醒你在哪里在文章中使用了这个类。

例如,AssertEx.DoesntThrow

[TestMethod]
public void LoadAssemblyBeforeImpersonatingSoYouDontLazyLoadInTheWrongSecurityContext()
{
	Class1 test = null;
	using(Impersonation.Impersonate(TestUser.UserName, TestUser.Password))
	{
		AssertEx.DoesntThrow(() =>
		{
			Class1 hello = new Class1();
		});
	}
}

你可能会问:这个方法有什么意义?尤其当你看到它的实现时。

public static void DoesntThrow(Action action)
{
	action();
}

有两个原因

首先,它向用户准确地展示了我期望的行为。这样她/他就不会过度关注不那么重要的部分,如 Impersonation.Impersonate(TestUser.UserName, TestUser.Password)

其次,在这种特定情况下,使用 lambda 表达式可能会改变结果。请记住,在它之前的测试中,使用 lambda 会在 TestUser 的安全上下文中加载 Class1 的程序集。

[TestMethod]
public void LazyAssemblyInAImpersonatedSecurityContextWithoutFileRightsThrows()
{
	using(Impersonation.Impersonate(TestUser.UserName, TestUser.Password))
	{
		AssertEx.Throws<FileLoadException>(() =>
		{
			Class1 hello = new Class1();
		});
	}
}

AssertEx.Throws 断言你的 lambda 抛出了正确的异常。

结论:你拥有并经常使用哪些实用类?

希望你会喜欢,但我很好奇你们。你最好的个人实用类是什么?

© . All rights reserved.