Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
EssOEss
Oct 23, 2006
128-bit approved

Eggnogium posted:

Yes, this NuGet package isn't used to build projects at all. It contains (or would contain, haven't built the package yet) an exe that is run after all the projects are built to deploy the combined application, in a post-build step of the TFS build workflow.

Right now I'm leaning towards just breaking best practices and throwing the exe and all its dependencies in a single solution-level package, then running it out of the packages folder.

I suppose my post-build scripts are a similar situation. I have a separate project that houses the post-build logic and scripts, which I ensure (by project dependencies) is always built last and (by deleting the post-build project's build output when running the script) is always built. Importing some of this logic via NuGet would be a logical step for me in this scenario, so if your situation seems similar, I would recommend the "dummy project" approach.

Adbot
ADBOT LOVES YOU

epswing
Nov 4, 2003

Soiled Meat

chmods please posted:

Doesn't log4net include a trace appender already though?

It does, but it appears to just use Trace.Write no matter the log level. DiagnosticsTraceAppender just shoves the log message into the correct Diagnostics log level.

gariig
Dec 31, 2004
Beaten into submission by my fiance
Pillbug
Since so many people have asked about asynchronous and parallel programming this week I'll plug Concurrency in C# as a great book to go over modern (.NET 4+) concurrency techniques. It's in a cookbook style approach where each chapter has a theme (async/await, TPL, Rx) and a bunch of examples that build on each other. Excellent book.

Adahn the nameless
Jul 12, 2006

Ithaqua posted:

Don't do this from build. Use a real release tool.

What would you use for this?

Leyburn
Aug 31, 2001
I'm unit testing the business logic of a personal project I've been working on for a few months and there's something that's been niggling at me. Be warned that this question is completely unimportant and mundane, but part of the point of doing this project is to help me decide on the 'right' way of doing things and so I'm allowing myself to obsess over minutiae in a way that I wouldn't normally do if I was doing this as part of an actual job.

The problem stems from the fact that I'm trying to following a guideline of only having one assert per-unit test, so that each test only checks a single rule. But the reality is that some of the methods have several outcomes if they run successfully, which is leading to an ugly amount of code duplication.

For reference the project is a web based clone of the GBA turn-based strategy game Advance Wars. The code below tests various outcomes of the AttackUnit() method when it is run successfully and it should demonstrate my annoyance.

C# code:
public class attack_unit
{
	[Test]
	public void with_valid_details_returns_expected_attack_results()
	{
		var attackingUnitId = Guid.NewGuid();
		var defendingUnitId = Guid.NewGuid();
		var expectedAttackResult = new AttackResult
		{
			AttackingUnitId = attackingUnitId,
			DefendingUnitId = defendingUnitId,
			AttackingUnitDamageTaken = 0,
			DefendingUnitDamageTaken = 7,
			AttackingUnitHitPointsRemaining = 10,
			DefendingUnitHitPointsRemaining = 3
		};
		var attackingUnitOwner = UserAccountHelper.CreateUserAccount();
		var defendingUnitOwner = UserAccountHelper.CreateUserAccount();
		var grid = GridHelper.CreateGrid(attackingUnitOwner, defendingUnitOwner);
		var cellRepository = CellHelper.CreateMockCellRepository();
		var cells = CellHelper.CreateAndSetupCellsByDimensions(cellRepository, grid, 2, 2);
		var unitRepository =
			UnitHelper
				.CreateMockUnitRepository()
				.SetupUnitByIdAtSpecifiedCell(attackingUnitId, 0, 0, cells, attackingUnitOwner, true, true, 10)
				.SetupUnitByIdAtSpecifiedCell(defendingUnitId, 0, 1, cells, defendingUnitOwner, true, true, 10);
		var unitService =
			UnitHelper
				.BuildUnitService(CurrentUserAccountProvider, unitRepository, cellRepository);
		CurrentUserAccountProvider.CurrentUserAccount = attackingUnitOwner;
		var results = unitService.AttackUnit(attackingUnitId, defendingUnitId);
		Assert.AreEqual(expectedAttackResult, results);
	}

	[Test]
	public void with_valid_details_updates_the_defending_units_hit_points()
	{
		var attackingUnitId = Guid.NewGuid();
		var defendingUnitId = Guid.NewGuid();
		var attackingUnitOwner = UserAccountHelper.CreateUserAccount();
		var defendingUnitOwner = UserAccountHelper.CreateUserAccount();
		var grid = GridHelper.CreateGrid(attackingUnitOwner, defendingUnitOwner);
		var cellRepository = CellHelper.CreateMockCellRepository();
		var cells = CellHelper.CreateAndSetupCellsByDimensions(cellRepository, grid, 2, 2);
		var unitRepository =
			UnitHelper
				.CreateMockUnitRepository()
				.SetupUnitByIdAtSpecifiedCell(attackingUnitId, 0, 0, cells, attackingUnitOwner, true, true, 10)
				.SetupUnitByIdAtSpecifiedCell(defendingUnitId, 0, 1, cells, defendingUnitOwner, true, true, 10);
		var unitService =
			UnitHelper
				.BuildUnitService(CurrentUserAccountProvider, unitRepository, cellRepository);
		CurrentUserAccountProvider.CurrentUserAccount = attackingUnitOwner;
		unitService.AttackUnit(attackingUnitId, defendingUnitId);
		unitRepository.Verify(r => r.UpdateHitPoints(defendingUnitId, 3));
	}

	[Test]
	public void with_valid_details_marks_attacking_unit_as_unable_to_move_and_attack()
	{
		var attackingUnitId = Guid.NewGuid();
		var defendingUnitId = Guid.NewGuid();
		var attackingUnitOwner = UserAccountHelper.CreateUserAccount();
		var defendingUnitOwner = UserAccountHelper.CreateUserAccount();
		var grid = GridHelper.CreateGrid(attackingUnitOwner, defendingUnitOwner);
		var cellRepository = CellHelper.CreateMockCellRepository();
		var cells = CellHelper.CreateAndSetupCellsByDimensions(cellRepository, grid, 2, 2);
		var unitRepository =
			UnitHelper
				.CreateMockUnitRepository()
				.SetupUnitByIdAtSpecifiedCell(attackingUnitId, 0, 0, cells, attackingUnitOwner, true, true, 10)
				.SetupUnitByIdAtSpecifiedCell(defendingUnitId, 0, 1, cells, defendingUnitOwner, true, true, 10);
		var unitService =
			UnitHelper
				.BuildUnitService(CurrentUserAccountProvider, unitRepository, cellRepository);
		CurrentUserAccountProvider.CurrentUserAccount = attackingUnitOwner;
		unitService.AttackUnit(attackingUnitId, defendingUnitId);
		unitRepository.Verify(r => r.UpdateCanAttack(attackingUnitId, false));
		unitRepository.Verify(r => r.UpdateCanMove(attackingUnitId, false));
	}
}
Although I've written this as three separate tests, the truth is that this could easily be a single test with 5 asserts at the end (which would trim this code from around 70 lines to about 30). What's the general best practice here? Should I make this a single test? Or is splitting it up like this regarded as okay?

The only thing that's really causing me to doubt myself here is that the preconditions for all three tests are identical, so any small change in how the application works would require the same changes to be made in three different places instead of one, which immediately sets off alarm bells. But the advantages of doing it as above is that when I run the tests I get a very clear and precise description of all the expected behavior of this method.

I realize this is ultimately a fairly trivial decision, but any input is appreciated. I've not had a great deal of experience writing unit tests so it's something I am trying to learn more about.

Edit: Also let me know if that code belongs in the coding horrors thread :/

Leyburn fucked around with this message at 13:37 on Sep 13, 2014

Che Delilas
Nov 23, 2009
FREE TIBET WEED

Leyburn posted:

The only thing that's really causing me to doubt myself here is that the preconditions for all three tests are identical, so any small change in how the application works would require the same changes to be made in three different places instead of one, which immediately sets off alarm bells. But the advantages of doing it as above is that when I run the tests I get a very clear and precise description of all the expected behavior of this method.

What do we do when we are using identical code in multiple places? Write a function.

Leyburn
Aug 31, 2001
Yeah that probably is the best thing to do. I was worried about sacrificing the readability of the tests, but you're right, that's probably not going to be a big problem in this case.

Che Delilas
Nov 23, 2009
FREE TIBET WEED

Leyburn posted:

Yeah that probably is the best thing to do. I was worried about sacrificing the readability of the tests, but you're right, that's probably not going to be a big problem in this case.

I think your tests are actually going to be far more readable once you encapsulate out all your Arrange logic into a separate PreapareUnitToAttack method or something similar.

Chill Callahan
Nov 14, 2012
Yeah. Read up on this: http://blog.8thlight.com/uncle-bob/2013/09/23/Test-first.html

I don't agree 100% with everything he says but tests are much more readable once they're refactored into a "given, when, then" structure.

Leyburn
Aug 31, 2001
Yeah that's exactly the kind of structure I've been aiming for with these tests. I was a bit hesitant to hide the setup section of these tests away in methods because it reduced my ability to see exactly what each test does at a glance, but I guess with properly named methods that shouldn't be an issue. The 'Given' steps of these particular tests are sufficiently complex to justify hiding it away.

Very quickly refactoring the tests to encapsulate the common setup code leaves me with something like this:

C# code:
[Test]
public void with_valid_details_returns_expected_attack_results()
{
  var attackingUnitId = Guid.NewGuid();
  var defendingUnitId = Guid.NewGuid();
  var unitService = PrepareAttackingAndDefendingUnits(attackingUnitId, defendingUnitId);
  var results = unitService.AttackUnit(attackingUnitId, defendingUnitId);
  AssertExpectedAttackResults(attackingUnitId, defendingUnitId, results);
}

[Test]
public void with_valid_details_updates_the_defending_units_hit_points()
{
  Mock<IUnitRepository> unitRepository;
  var attackingUnitId = Guid.NewGuid();
  var defendingUnitId = Guid.NewGuid();
  var unitService = PrepareAttackingAndDefendingUnits(attackingUnitId, defendingUnitId, out unitRepository);
  unitService.AttackUnit(attackingUnitId, defendingUnitId);
  unitRepository.Verify(r => r.UpdateHitPoints(defendingUnitId, 3));
}

[Test]
public void with_valid_details_marks_attacking_unit_as_unable_to_move_and_attack()
{
  Mock<IUnitRepository> unitRepository;
  var attackingUnitId = Guid.NewGuid();
  var defendingUnitId = Guid.NewGuid();
  var unitService = PrepareAttackingAndDefendingUnits(attackingUnitId, defendingUnitId, out unitRepository);
  unitService.AttackUnit(attackingUnitId, defendingUnitId);
  unitRepository.Verify(r => r.UpdateCanAttack(attackingUnitId, false));
  unitRepository.Verify(r => r.UpdateCanMove(attackingUnitId, false));
}
Of course now it is the use of the out parameter to expose the mocked repository that is bothering me, there are other ways around that problem however. I'll need to have a bit of a think about how I want to do it

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

Leyburn posted:

Of course now it is the use of the out parameter to expose the mocked repository that is bothering me, there are other ways around that problem however. I'll need to have a bit of a think about how I want to do it

Make two methods. One returns the repo, one returns the service.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

Adahn the nameless posted:

What would you use for this?

If you want to stay in the Microsoft stack and keep it simple (and can afford licensing costs), MS Release Management fits the bill. It can kick off a custom workflow-based deployment sequence, a DSC script, or Chef.

DSC, Chef, or Puppet are all attractive and reasonable options here.

Destroyenator
Dec 27, 2004

Don't ask me lady, I live in beer

Ithaqua posted:

If you want to stay in the Microsoft stack and keep it simple (and can afford licensing costs), MS Release Management fits the bill. It can kick off a custom workflow-based deployment sequence, a DSC script, or Chef.

DSC, Chef, or Puppet are all attractive and reasonable options here.
If you're doing web stuff Octopus Deploy is also pretty good. It can handle setting up IIS correctly and doing web config transforms / connection strings etc.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

Destroyenator posted:

If you're doing web stuff Octopus Deploy is also pretty good. It can handle setting up IIS correctly and doing web config transforms / connection strings etc.

Yeah, RM can do config file transforms (in a way) and it has built-in stuff for lots of Windows stuff. SSDT publishing, IIS, service installation, etc. They added Chef and DSC support in the last quarterly update, along with support to publishing directly to Azure IaaS VMs if you care about that. I think Microsoft's end goal is making continuous delivery something that you can set up really easily as long as you're using their products from end to end. Which makes sense.

I don't have any Octopus Deploy experience, but it looks solid.

zokie
Feb 13, 2006

Out of many, Sweden
I thought it was only assert over one object/mock and sub stub the rest. Not only one assert...

EssOEss
Oct 23, 2006
128-bit approved
Yeah, one assert is really overkill - you will hate yourself once you start to actually maintain these tests. I tend to focus more on "one scenario" - e.g. if I have some billing code, I check that the generated invoice has all its fields filled with the right data in one test case.

Green_Machine
Jun 28, 2008
There's a framework ideal for writing scenario-based BDD-style unit test that ties into MSTest. It's called Given.

Each [TestMethod] ideally has one assert, but the givens and whens are reusable across each test method so it's not painful to maintain at all. When you execute your tests the framework prints a textual output for each scenario like:

Given x
and y
and z
When something happens,
Then a
and b
and c

Where a, b, and c are your [TestMethod]s.

It's my favorite way to write tests because you end up with an easily-readable textual summary for each test that explains how the component-under-test is supposed to work. It's tests as code documentation.

Destroyenator
Dec 27, 2004

Don't ask me lady, I live in beer

Ithaqua posted:

RM...
I don't have any Octopus Deploy experience, but it looks solid.
Yeah, I've used Octopus on a couple of projects and been happy with it. Looks like I need to look at the MS stack again though.

Green_Machine posted:

There's a framework ideal for writing scenario-based BDD-style unit test that ties into MSTest. It's called Given.
This looks a lot like BDDfy. I haven't used it myself, NUnit is usually enough for me.

IratelyBlank
Dec 2, 2004
The only easy day was yesterday
I'm having a weird issue with a double value drifting in a for loop. I wrote a C# program to control another program and the details don't really matter but after some amount of time my double drifts by ~0.00000000000001 and it is messing with my measurements a little bit.

In the most recent case, the loop processed 32 times, incrementing a double starting at 1.95 by 0.05 each time. The 30th value was 3.55 and the 31st value was 3.44999999999999. The code is pretty straightforward and I only change the value in one place so I'm not sure what is going on.

code:
            // Iris coupling
            double size = 1.95;

            for (int i = 1; i <= 80; i++)
            {
                // Change iris size
                foreach (DataRow dr in data.Rows)
                {
                    if (dr["Shape"].ToString() == "Iris")
                    {
                        size += .05;
                        dr["SizeY"] = size;
                        break;
                    }
                }

                string[] sp = size.ToString().Split('.');

                if (sp.Length == 2)
                    cmbProject.Text = "Iris_" + sp[0] + "_" + sp[1] + "_mm_34_8x_13";
                else
                    cmbProject.Text = "Iris_" + sp[0] + "_0_mm_34_8x_13";

                btnGenerate.PerformClick();

                
                Process[] pname = Process.GetProcessesByName("HFSS");
                while (!(pname[0].HasExited))
                {
                    Thread.Sleep(1000);
                }
            }

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

IratelyBlank posted:

I'm having a weird issue with a double value drifting in a for loop.

It's not weird. Doubles are not exact.

http://stackoverflow.com/questions/1165761/decimal-vs-double-which-one-should-i-use-and-when

[edit]
Also, this is really ugly:
cmbProject.Text = "Iris_" + sp[0] + "_" + sp[1] + "_mm_34_8x_13";

Try cmbProject.Text = string.Format("Iris_{0}_{1}_mm_34_8x_13", sp[0], sp[1]);

New Yorp New Yorp fucked around with this message at 20:30 on Sep 14, 2014

IratelyBlank
Dec 2, 2004
The only easy day was yesterday

Ithaqua posted:

It's not weird. Doubles are not exact.

http://stackoverflow.com/questions/1165761/decimal-vs-double-which-one-should-i-use-and-when

[edit]
Also, this is really ugly:
cmbProject.Text = "Iris_" + sp[0] + "_" + sp[1] + "_mm_34_8x_13";

Try cmbProject.Text = string.Format("Iris_{0}_{1}_mm_34_8x_13", sp[0], sp[1]);

Ah ha, that was simple. Thanks for the help.

Sedro
Dec 31, 2008

IratelyBlank posted:

I'm having a weird issue with a double value drifting in a for loop. I wrote a C# program to control another program and the details don't really matter but after some amount of time my double drifts by ~0.00000000000001 and it is messing with my measurements a little bit.

Floating point addition and multiplication aren't associative because every computation may result in a loss of precision.

Your loop compounds the precision errors from previous loop iterations. You need to rewrite your loop so that size does not depend on the previous iteration.

Wrong:
C# code:
double size = 1.95;

for (int i = 1; i <= 80; ++i) {
  size += .05;
  // use size
}
Right:
C# code:
double initial = 1.95;

for (int i = 1; i <= 80; ++i) {
  double size = initial + i * .05;
  // use size
}
Increasing the size of the number from double (64-bit) to decimal (128-bit) does not fix the problem.

Horn
Jun 18, 2004

Penetration is the key to success
College Slice
Fixing the loop to use an int is certainly the right way to go but you should read up on the difference between float/double and decimal. Basically if you care about being exact (ie storing money) you should be using decimals.

http://stackoverflow.com/a/618596

Gul Banana
Nov 28, 2003

why is MEF2 so underdocumented and kind of insane. i'm totally behind the tradeoffs they made like dropping private-import stuff, focusing on startup performance, that's all good and i'd like to move to it - but there's no actual way to find out how to USE stuff like new concrete metadata views. also, it isn't CLS-compliant, and they close all issues saying they're not "targetting" that??

Jethro
Jun 1, 2000

I was raised on the dairy, Bitch!

Sedro posted:

Floating point addition and multiplication aren't associative because every computation may result in a loss of precision.

Your loop compounds the precision errors from previous loop iterations. You need to rewrite your loop so that size does not depend on the previous iteration.

Wrong:
C# code:
double size = 1.95;

for (int i = 1; i <= 80; ++i) {
  size += .05;
  // use size
}
Right:
C# code:
double initial = 1.95;

for (int i = 1; i <= 80; ++i) {
  double size = initial + i * .05;
  // use size
}
Increasing the size of the number from double (64-bit) to decimal (128-bit) does not fix the problem.

Changing to Decimal would fix this particular problem because Decimal stores a number in such a way that it can be represented in base 10 without loss of precision.

Fuck them
Jan 21, 2011

and their bullshit
:yotj:
Got a hosed up query (sigh) I want to do as close to right as I can in LINQ.

As it stands I'm basically doing this:

code:
      Dim items = (From t In context.trItems
                     Join c In context.trCustomers On t.trCustomerID Equals c.trcustomerid
                     Join i In context.trTypes On t.trTypeID Equals i.trTypeID
                     Where t.ref1.Contains(caseNo)
                     Order By t.ref1
                     Select c.Center, c.company, i.Category, t.ref1, t.ItemCode, t.ref2, 
t.ref3, t.location, t.ref4, t.ref6, t.ref7, t.nameout, t.updatedate, t.trCustomerID)
.Skip(startRow).Take(pageSize)
...and I want to add two optional parameters, which will basically match values on c.company and i.category. Either, both, or neither could be included, but that's not all. The really fun is that category could be one of four things, and three of them have multiple values for the keys in the database, because the db is corrupt or just never had input validation. If I was doing this in SQL I'd just use OR - in LINQ, what's the idiom for saying "where c.category is 1, 2, 13, 20, 21, or 23?"

Edit: did some research and realized LINQ lets you match on lists! So I made a function to take some id for a list and spit out said list, and want to do it like so:

code:
        Dim items = (//code from above).Skip(startRow).Take(pageSize)

        If _company <> 0 Then
            items = items.Where(Function(x) x.trTypeID = CategoryMapper(_company))
        End If

        If Not _category <> 0 Then
            items = items.Where(Function(y) y.trCustomerID = CompanyMapper(_category))
        End If
I've seen examples matching with a .Where against any element of a list, but in vb.net the compiler says Operator '=' is not defined for types 'Integer' and 'System.Collections.Generic.List(Of Integer)'. What's the syntax to do what I want?

vvv and I just edited, hah. But yes I am passing in a list from a private function.

Fuck them fucked around with this message at 16:17 on Sep 15, 2014

Opulent Ceremony
Feb 22, 2012

gently caress them posted:

in LINQ, what's the idiom for saying "where c.category is 1, 2, 13, 20, 21, or 23?"

If I'm reading you right: new List<int>{ 1, 2, 13, 20, 21, 23 }.Contains(c.category). Works for LINQ to Entities if you define the list outside the LINQ code.

Fuck them
Jan 21, 2011

and their bullshit
:yotj:

Opulent Ceremony posted:

If I'm reading you right: new List<int>{ 1, 2, 13, 20, 21, 23 }.Contains(c.category). Works for LINQ to Entities if you define the list outside the LINQ code.

Would items = items.Where(Function(x) x.trTypeID.Equals(CategoryMapper(_company))) work?

Opulent Ceremony
Feb 22, 2012

gently caress them posted:

Would items = items.Where(Function(x) x.trTypeID.Equals(CategoryMapper(_company))) work?

CategoryMapper is returning the List right? So it doesn't make sense to compare a single int to a List of ints. If you just want to further filter your items so that only those whose trTypeID is within the list returned from CategoryMapper, this should work:

items = items.Where(Function(x) CategoryMapper(_company).Contains(x.trTypeID))

Although probably assign the return value of CategoryMapper somewhere beforehand if using that function takes any work, you don't want to execute it for every item in your items list.

Fuck them
Jan 21, 2011

and their bullshit
:yotj:
That worked great, thank you.

It's still weird letting LINQ just do it all for me, but it definitely works when it does.

mortarr
Apr 28, 2005

frozen meat at high speed

gently caress them posted:

That worked great, thank you.

It's still weird letting LINQ just do it all for me, but it definitely works when it does.

If you're after optional conditions in the where clause, there's always predicate builder.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

mortarr posted:

If you're after optional conditions in the where clause, there's always predicate builder.

I highly recommend this library. I've used it in a few places when dynamically building expression trees and it's always worked as expected.

raminasi
Jan 25, 2005

a last drink with no ice

mortarr posted:

If you're after optional conditions in the where clause, there's always predicate builder.

I don't have any need for this right now, but I have a question about their example code:
C# code:
IQueryable<Product> SearchProducts (params string[] keywords)
{
  IQueryable<Product> query = dataContext.Products;

  foreach (string keyword in keywords)
  {
    string temp = keyword;
    query = query.Where (p => p.Description.Contains (temp));
  }
  return query;
}

quote:

The temporary variable in the loop is required to avoid the outer variable trap, where the same variable is captured for each iteration of the foreach loop.

Why would the same variable be captured on each iteration?

Funking Giblet
Jun 28, 2004

Jiglightful!
It's not executed on each iteration, so the reference pointer changes to the last variable to be set as the predicate.(It really hoists the variable outside the loop and you reassign it every iteration).
Assigning a variable inside the loop creates a new pointer for each iteration.
Closures!

Funking Giblet fucked around with this message at 15:10 on Sep 16, 2014

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

GrumpyDoctor posted:

I don't have any need for this right now, but I have a question about their example code:
C# code:
IQueryable<Product> SearchProducts (params string[] keywords)
{
  IQueryable<Product> query = dataContext.Products;

  foreach (string keyword in keywords)
  {
    string temp = keyword;
    query = query.Where (p => p.Description.Contains (temp));
  }
  return query;
}
Why would the same variable be captured on each iteration?

That code doesn't make any sense, anyway. Only the result of the last query is returned.
It's equivalent (as far as I can tell) to:

code:
IQueryable<Product> SearchProducts (params string[] keywords)
{
  return dataContext.Products.Where (p => p.Description.Contains (keywords.Last()));
}

ljw1004
Jan 18, 2005

rum

GrumpyDoctor posted:

I don't have any need for this right now, but I have a question about their example code:

Why would the same variable be captured on each iteration?

It's not captured on each iteration any longer. We made this change is VS2012. The temporary is no longer needed.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

ljw1004 posted:

It's not captured on each iteration any longer. We made this change is VS2012. The temporary is no longer needed.

I assume that's a C# 5 compiler thing, so it doesn't matter which framework version you're using, right?

SirViver
Oct 22, 2008

Ithaqua posted:

That code doesn't make any sense, anyway. Only the result of the last query is returned.
It's equivalent (as far as I can tell) to:
No, that's not what it does (assuming IQueryable behaves the same as LINQ to Objects). The loop "appends" the Where() filter for each keyword to the query, so you end up with an AND connection of the keywords.

However, incidentally it would've had the behavior you described if the sample hadn't used the temporary variable and you compiled the code on VS2010; you'd still get an AND linked chain of filters, but all filters would search for the last keyword only due to an implementation detail regarding foreach variables in older C# compilers.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

SirViver posted:

No, that's not what it does (assuming IQueryable behaves the same as LINQ to Objects). The loop "appends" the Where() filter for each keyword to the query, so you end up with an AND connection of the keywords.

However, incidentally it would've had the behavior you described if the sample hadn't used the temporary variable and you compiled the code on VS2010; you'd still get an AND linked chain of filters, but all filters would search for the last keyword only due to an implementation detail regarding foreach variables in older C# compilers.

Huh, I didn't realize Linq to objects did that. I'll have to test it out.

Adbot
ADBOT LOVES YOU

raminasi
Jan 25, 2005

a last drink with no ice

ljw1004 posted:

It's not captured on each iteration any longer. We made this change is VS2012. The temporary is no longer needed.

Was the old behavior originally intended, or was it a bug? That behavior seems incredibly counterintuitive to me, especially because, if I understand right, there's no way to explicitly request it anywhere in the language.

(Also, how did I not know that?)

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply