为什么要为类编写扩展方法而不是直接更改实现?
例如,如果我有课程
public class A
{
public void someMethod1() {...}
}
,我想添加更多功能,那为什么还要编写一个扩展方法,如:
public static class AExtensions
{
public static void someMethod2(this A a) {...}
}
不仅仅是将实现直接更改为
public class A
{
public void someMethod1() {...}
public static void someMethod2() {...}
}
扩展方法似乎比较麻烦。
答案 0 :(得分:7)
这个问题很奇怪;似乎是“当我知道如何以简单的方式做事时,为什么我会以困难的方式做事?”您不会,所以问为什么会是一件很奇怪的事情。您通常在控制扩展类型时将 not 放入扩展方法中。在这种情况下,并未发明扩展方法。它们是针对相反场景而发明的,在这种情况下,进行扩展的人员不控制类型。
但是,让我们来看看原始的发布者,看看在某些情况下即使在我们控制所有定义的情况下,将功能放入扩展方法中也是合理的。对我来说,目前尚不清楚为什么原始海报正在寻找使用此模式的借口,但我们不必为此担心。
这里是一个。遵循逻辑:
在这种情况下,即使我们控制类和接口,也可以将功能实现为扩展方法。
那可能很抽象。让我们看一个例子。我们希望实现一个可变的不可变堆栈。这是我们的首次尝试:
public abstract class Stack<T>
{
private Stack() {}
public static readonly Stack<T> Empty = new EmptyStack();
private sealed class EmptyStack : Stack<T>
{
public override bool IsEmpty => true;
public override T Peek() => throw new Exception("Empty stack");
public override Stack<T> Pop() => throw new Exception("Empty stack");
}
private sealed class Regular : Stack<T>
{
private readonly T head;
private readonly Stack<T> tail;
public Stack(T head, Stack<T> tail)
{
this.head = head;
this.tail = tail;
}
public override bool IsEmpty => false;
public override T Peek() => head;
public override Stack<T> Pop() => tail;
}
public abstract bool IsEmpty { get; }
public abstract T Peek();
public abstract Stack<T> Pop();
public Stack<T> Push(T head) => new Regular(head, this);
}
好的,第一个问题:我们不能在类类型上使用方差。它必须是一个接口。什么界面?
public interface IStack<out T>
{
bool IsEmpty { get; }
IStack<T> Pop();
T Peek();
到目前为止,协方差没有问题。如果我们有IStack<Mammal>
,则可以将其用作IStack<Animal>
,因为当我们偷看一堆狮子,老虎和熊时,每次都会得到一只动物。
但是Push
呢?我们不能写
IStack<T> Push(T t);
因为现在T的使用位置无效。
但是让我们考虑一下。假设我们有一个IStack<Turtle>
。 我们可以将长颈鹿推上去吗?听起来错了;您不能将长颈鹿放入海龟列表中,那么为什么您应该能够将长颈鹿推到一堆海龟上呢?
但这确实有效:一堆乌龟就是一堆动物,我们可以将长颈鹿推到上面。所以我们需要的是:
public interface IStack<out T>
{
...
IStack<U> Push<U>(U u) where T : U;
}
在C#中是非法的;没有这样的“向后”约束。(再次,Java可以做得更好的少数几个领域之一。)
好的,我们在界面中没有我们想要的Push
。那我们该如何推入堆栈呢?我们可以通过对类型进行少量更改来做到这一点:
public abstract class Stack<T> : IStack<T>
{
private Stack() {}
public static readonly Stack<T> Empty = new EmptyStack();
private sealed class EmptyStack : Stack<T>
{
public override bool IsEmpty => true;
public override T Peek() => throw new Exception("Empty stack");
public override IStack<T> Pop() => throw new Exception("Empty stack");
}
private sealed class Regular : Stack<T>
{
private readonly T head;
private readonly IStack<T> tail;
public Stack(T head, IStack<T> tail)
{
this.head = head;
this.tail = tail;
}
public override bool IsEmpty => false;
public override T Peek() => head;
public override IStack<T> Pop() => tail;
}
public abstract bool IsEmpty { get; }
public abstract T Peek();
public abstract IStack<T> Pop();
public static IStack<T> Push(T head, IStack<T> tail) =>
new Regular(head, tail);
}
超级。呼叫站点是什么样的?
IStack<Turtle> s1 = Stack<Turtle>.Empty;
IStack<Turtle> s2 = Stack<Turtle>.Push(someTurtle, s1);
IStack<Animal> s3 = Stack<Turtle>.Push(anotherTurtle, s2);
IStack<Animal> s4 = Stack<Animal>.Push(someGiraffe, s3);
完全有效。我们用一堆乌龟作为一堆动物,然后将长颈鹿推上去。但是,噢,我的天哪,看看那个呼叫站点有多可怕!
我们需要的是一种将类型参数从调用站点中移出的方法,但是我们可以使用...一种扩展方法来做到这一点!
public static IStack<T> Push<T>(this IStack<T> s, T t) =>
Stack<T>.Push(t, s);
现在我们的呼叫站点是什么样的?
IStack<Turtle> s1 = Stack<Turtle>.Empty;
IStack<Turtle> s2 = s1.Push(someTurtle);
IStack<Animal> s3 = s2.Push(anotherTurtle);
IStack<Animal> s4 = s3.Push(someGiraffe);
好多了。更好:
var s3 = Stack<Turtle>.Empty.Push(someTurtle).Push(anotherTurtle);
var s4 = s3.Push((Animal)someGiraffe);
(强制转换的要求有点不幸; C#类型推断不会得出“我有一只乌龟和长颈鹿,开发者可能是动物”的信息。相反,它会得出“我有一只乌龟和长颈鹿,并且我不知道该选择哪个。”强制转换可帮助编译器解决歧义。)
因此,要回答您的问题:为什么可以在修改类时实施扩展方法? 如果修改类使您陷入呼叫站点必须丑陋的情况,那么您会这样做,但是添加巧妙的扩展方法可以带来愉悦,流畅,令人愉悦的用户体验。
答案 1 :(得分:2)
这不仅与您无权修改自己的类有关,而且extension methods也可以在接口上工作,在接口上,事情会变得非常有趣(并且会带来许多LINQ优点)来自)。
因此,如果您有一个名为#define MAX_FILE_SIZE 0x10000000
void ExtendFileSection()
{
HANDLE hFile = CreateFile(L"d:/ee.tmp", GENERIC_ALL, 0, 0, CREATE_ALWAYS, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
HANDLE hSection;
SYSTEM_INFO info;
GetSystemInfo(&info);
// initially only 1 page in the file
LARGE_INTEGER SectionSize = { info.dwPageSize };
NTSTATUS status = NtCreateSection(&hSection,
SECTION_EXTEND_SIZE|SECTION_MAP_READ|SECTION_MAP_WRITE, 0,
&SectionSize, PAGE_READWRITE, SEC_COMMIT, hFile);
CloseHandle(hFile);
if (0 <= status)
{
PVOID BaseAddress = 0;
SIZE_T ViewSize = MAX_FILE_SIZE;
//MapViewOfFile(hSection, FILE_MAP_WRITE|FILE_MAP_RESERVE, 0, 0, MAX_FILE_SIZE);
status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &BaseAddress, 0, 0, 0,
&ViewSize, ViewUnmap, MEM_RESERVE, PAGE_READWRITE);
if (0 <= status)
{
SIZE_T n = MAX_FILE_SIZE / info.dwPageSize - 1;
do
{
SectionSize.QuadPart += info.dwPageSize;
if (0 > NtExtendSection(hSection, &SectionSize))
{
break;
}
} while (--n);
UnmapViewOfFile(BaseAddress);
}
CloseHandle(hSection);
}
}
}
的接口,则可以定义以下扩展类:
IEnumerable<T>
现在,只要有实现IEnumerable的任何地方,该扩展方法就可以使用。
您可以执行以下操作:public static class EnumerableExtensions
{
public static IEnumerable<T> ForEach(this IEnumerable<T> enumerable, Action<T> action)
{
foreach (T item in enumerable)
{
action(item);
}
return enumerable;
}
}
但是请注意我的扩展方法如何返回list.ForEach(x => Console.WriteLine(x.SomeProperty))
?这就是method chaining出现的地方:
IEnumerable<T>
答案 2 :(得分:1)
您可以在无权访问的类上使用扩展方法,例如,可以将扩展方法添加到String
中,并且不能修改String
,因为它是.NET的一部分。这也意味着您不必一直拥有它们-例如LINQ,它是扩展方法的集合,仅当您添加using
语句时才可用,这样它们才不会不必要地阻塞。它还使代码保持整洁,编写名为Reverse
的扩展方法,并且执行"Hello".Reverse();
比MyExtensionClass.Reverse("Hello");
更好,尤其是当它在类似ASP.NET页面的页面上出现50次时,通常必须处理渲染复杂模型时使用。