可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Given the following
class MyClass
{
private Expression<Func<SomeEntity, int>> _orderBy;
public MyClass(Expression<Func<SomeEntity, int>> orderBy)
{
_orderBy = orderBy;
}
public List<SomeEntity> Fetch()
{
return DbContext.Set<SomeEntity>().OrderBy(_orderBy).ToList();
}
}
// A function that creates an orderBy expression
Expression<Func<SomeEntity, int>> SomeFunction()
{
// r is a local variable for the sake of simplicity,
// actually it is a global static variable.
Random r = new Random();
int seed = r.nextInt();
// s.SomeProperty XOR seed, a simple random sorting method
return s => (s.SomeProperty & ~seed) | (~s.SomeProperty & seed);
}
And the executing code
var myClass = new MyClass( SomeFunction() );
List<SomeEntity> someList = myClass.Fetch();
Thread.Sleep(100000);
List<SomeEntity> anotherList = myClass.Fetch();
Each time Fetch() is called, a randomly sorted list must be returned. The problem is, seed = r.nextInt()
won't be called each time I call Fetch(). How do I ensure that a new seed is generated every time Fetch()
is called?
回答1:
One possible solution is pass in a factory function that generates the order by functions instead of the function itself, now it generates a new Expression<Func<SomeEntity, int>>
every time you call Fetch()
each with a new seed.
class MyClass
{
Func<Expression<Func<SomeEntity, int>>> _orderByFactory;
public MyClass(Func<Expression<Func<SomeEntity, int>>> orderByFactory)
{
_orderByFactory = orderByFactory;
}
public List<SomeEntity> Fetch()
{
var orderBy = _orderByFactory();
return DbContext.Set<SomeEntity>().OrderBy(orderBy).ToList();
}
}
//...
// Moved r out of the function as it needs to be outside for it to work.
static Random r = new Random();
static Expression<Func<SomeEntity, int>> SomeFunction()
{
int seed = r.Next();
// s.SomeProperty XOR seed, a simple random sorting method
return s => (s.SomeProperty & ~seed) | (~s.SomeProperty & seed);
}
//...
var myClass = new MyClass(SomeFunction); //<--Notice the removed parenthesis
List<SomeEntity> someList = myClass.Fetch();
Thread.Sleep(100000);
List<SomeEntity> anotherList = myClass.Fetch();
回答2:
Simply move the call to r.Next() into your delegate. The following code (a bit dumbed down from your code since I didn't want to have to actually order anything) returns different numbers each time. Sorry for the poor variable names...
FYI, the variable r
gets elevated out from the method SomeFunction()
onto the heap, and will stick around and be used any time the Expression> gets called. See This question and the link in the answer for more information on closures.
EDIT: I've moved the random number generation to its own class that seems to work...
internal class MyClass {
private Expression<Func<string, int>> _aProperty;
public MyClass(Expression<Func<string, int>> aProperty) {
_aProperty = aProperty;
}
public int Fetch() {
var something = _aProperty.Compile();
return something("doesn't matter");
}
}
public class DoubleUp {
private Random r = new Random();
private int currentValue;
public int A { get { return currentValue; } }
public int B { get { int tmp = currentValue; currentValue = r.Next(); return tmp; } }
public DoubleUp() {
currentValue = r.Next();
}
}
internal class Program {
private static Expression<Func<string, int>> SomeFunction() {
DoubleUp d = new DoubleUp();
return s => (d.A - d.B);
}
private static void Main(string[] args) {
var myClass = new MyClass(SomeFunction());
Console.WriteLine(myClass.Fetch());
Console.WriteLine(myClass.Fetch());
Console.WriteLine(myClass.Fetch());
Console.ReadLine();
}
}
回答3:
The problem is you only call SomeFunction
once to set the seed. And you can't reset the seed within the Expression because an expression cannot contain a function body.
An alternative would be to use 4 bytes of a new Guid as a pseudo-random number generator:
// A function that creates an orderBy expression
Expression<Func<int, int>> SomeFunction()
{
// Take 4 bytes from a new Guid and turn it into a 32-bit integer
return s=> BitConverter.ToInt32(Guid.NewGuid().ToByteArray(),0);
}
回答4:
Make the expression a Func that you call to get a new Orderby each time Fetch is called:
class MyClass
{
private Func<Expression<Func<SomeEntity, int>>> _orderByFactory;
public MyClass(Func<Expression<Func<SomeEntity, int>>> orderByFactory)
{
_orderByFactory = orderByFactory;
}
public List<SomeEntity> Fetch()
{
var orderBy = _orderByFactory();
return DbContext.Set<SomeEntity>().OrderBy(orderBy).ToList();
}
}
And the call becomes
var myClass = new MyClass( () => SomeFunction() );
List<SomeEntity> someList = myClass.Fetch();
回答5:
Given your problem statement:
Each time Fetch() is called, a randomly sorted list must be returned.
It seems to me you're over-thinking things. Why on earth wouldn't you just do something simple like:
class MyClass
{
private static Random rng = new Random() ;
public List<SomeEntity> Fetch()
{
return DbContext.Set<SomeEntity>()
.Cast<SomeEntity>()
.OrderBy( x => rng.Next() )
.ToList()
;
}
}